盘一盘 synchronized (一)―― 从打印Java对象头说起

匿名 (未验证) 提交于 2019-12-02 21:52:03

Java对象头的组成

值得注意的是,如果应用的对象过多,使用64位的指针将浪费大量内存。64位的JVM比32位的JVM多耗费50%的内存。
我们现在使用的64位 JVM会默认使用选项 +UseCompressedOops 开启指针压缩,将指针压缩至32位。
以64位操作系统为例,对象头存储内容图例。
|--------------------------------------------------------------------------------------------------------------| |                                              Object Header (128 bits)                                        | |--------------------------------------------------------------------------------------------------------------| |                        Mark Word (64 bits)                                    |      Klass Word (64 bits)    |        |--------------------------------------------------------------------------------------------------------------| |  unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 |     OOP to metadata object   |  无锁 |----------------------------------------------------------------------|--------|------------------------------| |  thread:54 |         epoch:2      | unused:1 | age:4 | biased_lock:1 | lock:2 |     OOP to metadata object   |  偏向锁 |----------------------------------------------------------------------|--------|------------------------------| |                     ptr_to_lock_record:62                            | lock:2 |     OOP to metadata object   |  轻量锁 |----------------------------------------------------------------------|--------|------------------------------| |                     ptr_to_heavyweight_monitor:62                    | lock:2 |     OOP to metadata object   |  重量锁 |----------------------------------------------------------------------|--------|------------------------------| |                                                                      | lock:2 |     OOP to metadata object   |    GC |--------------------------------------------------------------------------------------------------------------|
简单介绍一下各部分的含义
biased_lock:偏向锁标记,为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。
age:Java GC标记位对象年龄。
identity_hashcode:对象标识Hash码,采用延迟加载技术。当对象使用HashCode()计算后,并会将结果写到该对象头中。当对象被锁定时,该值会移动到线程Monitor中。
epoch:偏向时间戳。
ptr_to_lock_record:指向栈中锁记录的指针。
ptr_to_heavyweight_monitor:指向线程Monitor的指针。

使用JOL工具类,打印对象头

使用maven的方式,添加jol依赖

<dependency>   <groupId>org.openjdk.jol</groupId>   <artifactId>jol-core</artifactId>   <version>0.8</version> </dependency>

创建一个对象A

public class A {     boolean flag = false; }

使用jol工具类输出A对象的对象头

public static void main(String[] args){     A a = new A();     System.out.println(ClassLayout.parseInstance(a).toPrintable()); }

看看输出结果

输出的第一行内容和锁状态内容对应
unused:1 | age:4 | biased_lock:1 | lock:2
第四行则是我们创建的A对象属性信息 1字节的boolean值

偏向锁

public static void main(String[] args) throws InterruptedException {     Thread.sleep(5000);     A a = new A();     System.out.println(ClassLayout.parseInstance(a).toPrintable()); }

输出结果

刚开始使用这段代码我是震惊的,为什么睡眠了5s中就把活生生的A对象由无锁状态改变成为偏向锁了呢?别急,容我慢慢道来!
可能你又要问了,我这也没使用synchronized关键字呀,那不也应该是无锁么?怎么会是偏向锁呢?
特殊状态的无锁
大家可以看下面这张图理解一下对象头的状态的创建过程

再来看看这段代码,使用了synchronized关键字
public static void main(String[] args) throws InterruptedException {     Thread.sleep(5000);     A a = new A();     synchronized (a){         System.out.println(ClassLayout.parseInstance(a).toPrintable());     } }

此时对象a,对象头内容有了明显的变化,当前偏向锁偏向主线程。

轻量级锁

public static void main(String[] args) throws InterruptedException {     Thread.sleep(5000);     A a = new A();      Thread thread1 = new Thread(){         @Override         public void run() {             synchronized (a){                 System.out.println("thread1 locking");                 System.out.println(ClassLayout.parseInstance(a).toPrintable());             }             try {                 //thread1退出同步代码块,且没有死亡                 Thread.sleep(3000);             } catch (InterruptedException e) {                 e.printStackTrace();             }         }     };      Thread thread2 = new Thread(){         @Override         public void run() {             synchronized (a){                 System.out.println("thread2 locking");                 System.out.println(ClassLayout.parseInstance(a).toPrintable());             }         }     };     thread1.start();         //让thread1执行完同步代码块中方法。     Thread.sleep(3000);     thread2.start(); }

thread1中依旧输出偏向锁,thread2获取对象A时,thread1已经退出同步代码块,故此时thread2输出结果为轻量级锁。

重量级锁

public static void main(String[] args) throws InterruptedException {     Thread.sleep(5000);     A a = new A();     Thread thread1 = new Thread(){         @Override         public void run() {             synchronized (a){                 System.out.println("thread1 locking");                 System.out.println(ClassLayout.parseInstance(a).toPrintable());                 try {                     //让线程晚点儿死亡,造成锁的竞争                     Thread.sleep(2000);                 } catch (InterruptedException e) {                     e.printStackTrace();                 }             }         }     };     Thread thread2 = new Thread(){         @Override         public void run() {             synchronized (a){                 System.out.println("thread2 locking");                 System.out.println(ClassLayout.parseInstance(a).toPrintable());                 try {                     Thread.sleep(2000);                 } catch (InterruptedException e) {                     e.printStackTrace();                 }             }         }     };     thread1.start();     thread2.start(); }

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!