一. 对象内存构成
对象的组成组成
JVM 中,Java对象保存在堆中时,由以下三部分组成:
- 对象头(object header):包括了关于堆对象的布局、类型、GC状态、同步状态和标识哈希码的基本信息(12byte)。
对象头由三部分组成:
1,Mark Word
2,指向类的指针(指向元空间)
3,数组长度(只有数组对象才有)
- 实例数据(Instance Data):主要是存放类的数据信息,父类的信息,对象字段属性信息。
- 对齐填充(Padding):为了字节对齐,填充的数据,不是必须的(对象的大小是2^3的整数倍,时间换空间)。
Object o = new Object();在内存中占多大内存?
在开启压缩的情况下,对象头(object header)占据12byte(96bit),其中 mark word占8byte(64bit),klass pointe 占4byte,另外剩余4byte是填充对齐的。对象的引用在栈内存占4byte,总共是16+4=20byte
对象
Mark Word在64位JVM中的存储
锁升级:
- 当没有被使用时,这就是一个普通的对象,Mark Word记录对象的HashCode(调用HashCode方法时会写入,调用之前为空),锁标志位是01,是否偏向锁那一位是0。
- 当对象被当做同步锁并有一个线程A抢到了锁时,锁标志位还是01,但是否偏向锁那一位改成1,前54bit记录抢到锁的线程id,表示进入偏向锁状态。
- 当线程A再次试图来获得锁时,JVM发现同步锁对象的标志位是01,是否偏向锁是1,也就是偏向状态,Mark Word中记录的线程id就是线程A自己的id,表示线程A已经获得了这个偏向锁,可以执行同步锁的代码。
- 当线程B试图获得这个锁时,JVM发现同步锁处于偏向状态,但是Mark Word中的线程id记录的不是B,那么线程B会先用CAS操作试图获得锁,这里的获得锁操作是有可能成功的,因为线程A一般不会自动释放偏向锁。如果抢锁成功,就把Mark Word里的线程id改为线程B的id,代表线程B获得了这个偏向锁,可以执行同步锁代码。如果抢锁失败,则继续执行步骤5。
- 线程B在偏向锁状态抢锁失败,代表当前锁有一定的竞争,偏向锁将升级为轻量级锁。JVM会在当前线程的线程栈中开辟一块单独的空间,里面保存指向对象锁Mark Word的指针,同时在对象锁Mark Word中保存指向这片空间的指针。上述两个保存操作都是CAS操作,如果保存成功,代表线程抢到了同步锁,就把Mark Word中的锁标志位改成00,可以执行同步锁代码。如果保存失败,表示抢锁失败,竞争太激烈,继续执行步骤6。
- 轻量级锁抢锁失败,JVM会使用自旋锁,自旋锁不是一个锁状态,只是代表不断的重试,尝试抢锁。从JDK1.7开始,自旋锁默认启用,自旋次数由JVM决定。如果抢锁成功则执行同步锁代码,如果失败则继续执行步骤7。
- 自旋锁重试之后如果抢锁依然失败,同步锁会升级至重量级锁,锁标志位改为10。在这个状态下,未抢到锁的线程都会被阻塞。
锁降级:只会发生在GC的额时候,这时候只有GC线程使用,没有讨论的意义
锁消除:只有一个线程在使用某个资源,JVM会消除这个资源的锁
锁粗化
while(i<1000){
a.show();
}
这时候JVM会将a的锁加到while上面
参考博客: