Java对象的内存结构
对象内存结构
在64位操作系统下,
MarkWord(下图_mark)占64位
KlassWord(下图_klass)占32位 64位系统的Klass Word不是32位,默认64位,开启指针压缩后为32(感谢评论老哥的指出)
64位系统的Klass Word不是32位,默认64位,开启指针压缩后为32
_lengh(只有数据对象才有,不考虑)
实例数据(下图instance data)看参数的类型,int就占32位(4byte)
补齐(padding)是JVM规定java对象内存必须是8byte的倍数,如果实例数据占2byte,那么(64bit的Markword+32bit的Klassword+实例数据32bit)=128bit=16byte是8byte的倍数,所以padding部分为0。
查看对象内存结构
JDK8
<dependency> <groupId>org.openjdk.jol</groupId> <artifactId>jol-core</artifactId> <version>0.9</version> </dependency>
public class SynchronizedDemo { public static void main(String[] args) { Dog dog = new Dog(); System.out.println(ClassLayout.parseInstance(dog).toPrintable()); } } class Dog { char age; }
如上图所示,对象头中的MarkWord占8byte,KlassWord占4个byte,实例属性age是char类型占2个byte,那么此时加起来为14byte,为了满足是8的倍数,要补充2个byte。
下图是当Dog对象里的age变为int时打印的结果,请自行对比。
对象头
下图是引自《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版) 周志明》中的一个图,下图是32操作系统下的对象头中的Mark Word(32位),Klass Word(32位),一共是64位。
64操作系统下,Mark Word的长度是64,在加Klass Word(32位),一共是96位,其实对象头长什么样其实不是本文的重点,本文的重点是验证锁升级的过程,所以我们只需要关注对象头中Mark Word的最后3位即可,如下图中的后3位。
锁升级的过程
前提
由于大小端引起的问题,使得这里展示的高低位相反,如下图所示,所以我要关注的就是⑧位置的最后3位足矣。
无锁状态
public class SynchronizedDemo { public static void main(String[] args) { Dog dog = new Dog(); System.out.println(ClassLayout.parseInstance(dog).toPrintable()); } }
如下图所示,001表示的无锁状态并且不允许偏向 (其实默认是开启偏向的,只不过虚拟机后在运行后几秒才开启偏向锁)
使用下面的参数,如下图所示 ,会发现状态为101,表示无锁状态
-XX:BiasedLockingStartupDelay=0
由无锁状态---->偏向锁状态
单线程访问锁的时候,锁由无锁状态变为偏向锁状态。
// -XX:BiasedLockingStartupDelay=0 public class SynchronizedDemo { public static void main(String[] args) { Dog dog = new Dog(); System.out.println(ClassLayout.parseInstance(dog).toPrintable()); //上锁 synchronized (dog){ System.out.println(ClassLayout.parseInstance(dog).toPrintable()); } System.out.println(ClassLayout.parseInstance(dog).toPrintable()); } } class Dog { int age; }
如上图所示,开始状态为101,为可偏向,无锁状态
上锁后状态是101,为可偏向,有锁状态
解锁后:状态为101,为可偏向,有锁状态
区别为:当线程给无锁状态的lock加锁时,会把线程ID存储到MarkWord中,即锁偏向于该ID的线程,偏向锁不会自动释放。
上面表格中2->3的过程。
偏向锁状态---->轻量级锁状态
多线程使用锁(不竞争,错开时间访问),锁由偏向锁状态变为轻量级锁状态
// -XX:BiasedLockingStartupDelay=0 public class SynchronizedDemo { public static void main(String[] args) { Dog dog = new Dog(); System.out.println("初始状态:"); System.out.println(ClassLayout.parseInstance(dog).toPrintable()); new Thread( () -> { synchronized (dog) { System.out.println("hello world"); } }, "t1") .start(); System.out.println("线程1释放锁后:"); System.out.println(ClassLayout.parseInstance(dog).toPrintable()); try { TimeUnit.SECONDS.sleep(3); } catch (Exception e) { e.printStackTrace(); } finally { } new Thread( () -> { synchronized (dog) { System.out.println("线程2上锁:"); System.out.println(ClassLayout.parseInstance(dog).toPrintable()); } System.out.println("线程2释放锁:"); System.out.println(ClassLayout.parseInstance(dog).toPrintable()); }, "t2") .start(); } } class Dog { int age; }
初始状态为101,为可偏向,并且为无锁状态
线程1释放锁后,状态为101,并且存储了线程ID,为偏向锁状态,偏向于线程1
线程2上锁,上锁后,状态为00,轻量级锁状态
线程2释放锁后,状态为001,此时为不可偏向的无锁状态。
重量级锁状态
// -XX:BiasedLockingStartupDelay=0 public class SynchronizedDemo { public static void main(String[] args) { Dog dog = new Dog(); System.out.println("初始状态:"); System.out.println(ClassLayout.parseInstance(dog).toPrintable()); new Thread( () -> { synchronized (dog) { System.out.println(""); try { TimeUnit.SECONDS.sleep(3); } catch (Exception e) { e.printStackTrace(); } finally { } } }, "t1") .start(); new Thread( () -> { synchronized (dog) { System.out.println("线程2上锁"); System.out.println(ClassLayout.parseInstance(dog).toPrintable()); try { TimeUnit.SECONDS.sleep(3); } catch (Exception e) { e.printStackTrace(); } finally { } } System.out.println("线程2释放锁"); System.out.println(ClassLayout.parseInstance(dog).toPrintable()); }, "t2") .start(); } } class Dog { int age; }
如上图所示,锁初始状态为101,可偏向无锁状态
当线程1在使用锁,而线程2去上锁的时候,状态已经变为010,不可偏向重量级锁。
总结
单线程使用锁的时候为偏向锁。
多线程无竞争(错峰使用锁)的时候为轻量级锁。
有竞争的时候为重量级锁。