对象头
synchronized使用锁对象是存储在对象头里面的。对象头由MarkWord
和Class MetaData Address
组成。对象头占12个字节(byte)
MarkWord包括哈希码,GC分代年龄,锁状态标志,线程持有的锁和线程的偏向ID。
Class MetaData Address 存储对象的类型指针,该指针指向它的类元数据。
synchronized的锁状态优化
无锁
这个阶段在对象头的存储是001,1bit记录了偏向锁位,01是记录了无锁阶段。
偏向锁
偏向锁时JDK1.6之后引进的锁优化。在无锁竞争的条件下,一个线程通过一次CAS尝试将对象头中Thread ID设置为自己的线程号,偏向锁的标准就被设置为1,锁标志位为01,这时候,锁的状态就位偏向锁,该线程获取成功。之后改线程只要持有的id与对象头设置的id一致,那么就可以直接访问同步代码块。
轻量级锁
当有第二个线程访问到时,锁的状态就有偏向锁改为轻量级锁。因为偏向锁不会主动释放锁,因此以后线程1在获取锁时,只需要比较当前Thread id是否与java对象头设置的id是否一致。如果不一致,那么就意味着这个有多个线程在访问同步代码块。而偏向锁不会主动释放第一个线程的Thread id,而是看线程1 是否还存活 。如果没有存活,那么锁对象会被重置为无锁,其他线程抢占式来获取锁,采用CAS来将对象头的Thread id设置为自己的id。如果线程1还在存活,说明存在锁竞争。锁会被挂起,其他线程会通过自旋来不停的尝试获取锁,竞争的线程不会阻塞。如果通过一定数量的CAS操作还是获取失败,表示此时存在其他线程竞争锁(两条或两条以上的线程竞争同一个锁),则轻量级锁会膨胀成重量级锁。
当锁膨胀到轻量级锁或重量级锁时,对象头中62位bit都会用来存储锁记录的地址 ,这些分代年龄、hashcode都会存与锁记录空间。而锁记录空间是存在于当前线程的栈帧中 。锁的状态会改为00,轻量级锁。虚拟机会使用CAS操作尝试把mark word指向当前的锁记录地址。
轻量级锁升级为重量级锁的条件
当自旋超过一定的次数(10次),或者一个线程在持有锁,一个在自旋,又有第三个来访时(发生了多个线程竞争锁),轻量级锁升级为重量级锁。锁记录有01改为10。
实例数据
存放类的属性数据信息,包括父类的属性信息,如果是数组的实例部分还包括数组的长度,这部分内存按 4 字节对齐。
对齐填充(非必须)
没有特别作用,起到占位符作用,因为HotSpot规定任何对象大小必须是8字节整数倍