一,java对象的内存布局
java对象放在堆内存中的,大致可以分为三个部分,分别是对象头,实例变量和对齐填充数据。
1.1 对象头
对象头包括两部分:标记字段 MarkWord 和 类型指针 Klass Pointer。
其中Klass Point是是对象指向它的类元数据(方法区)的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
Mark Word:默认存储对象的 HashCode,分代年龄和锁标志位信息
。这些信息都是与对象自身定义无关的数据,所以 Mark Word 被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据。它会根据对象的状态复用自己的存储空间,也就是说在运行期间 Mark Word 里存储的数据会随着锁标志位的变化而变化。它是实现轻量级锁和偏向锁的关键。
Mark Word会随着程序的运行发生变化,变化状态如下 (32位虚拟机):
可以看到,当对象状态为偏向锁时,Mark Word 存储的是偏向的线程ID;当状态为轻量锁时,Mark Word 存储的是指向线程栈中 Lock Record 的指针;当状态为重量锁时,Mark Word 指向堆中 monitor 对象的指针。
1.2 实例变量
存放类的属性数据信息,包括父类的属性信息,这部分内存按4字节对齐。
1.3 对齐填充数据
由于虚拟机要求对象起始地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐。
二,偏向锁、轻量级锁、自旋锁、重量级锁、锁取消、锁粗化
三,锁升级流程图
四,锁升级流程分析
每一个线程在准备获取共享资源时:
第一步,检查 MarkWord 里面是不是放的自己的 ThreadlD,如果是,表示当前线程是处于“偏向锁”;
第二步,如果 MarkWord ThreadlD 不是自己的,锁升级,这时候,用 CAS 来执行切换,新的线程根据 MarkWord ThreadlD 里面现有的,通知之前线程暂停,之前线程将 Markword 的内容置为空;
第三步,两个线程都把锁对象的 HashCode 复制到自己新建的用于存储锁的记录空间 Lock Record,接着开始通过 CAS 操作,把锁对象的 MarKWord 的内容修改为自己新建的记录空间的地址的方式竞争 MarkWord;
第四步,第三步中成功执行 CAS 的获得资源,失败的则进入自旋;
第五步,自旋的线程在自旋过程中,成功获得资源(即之前获的资源的线程执行完成并释放了共享资源),则整个状态依然处于轻量级锁的状态,如果自旋失败;
第六步,进入重量级锁的状态,这个时候,自旋的线程进行阻塞,等待之前线程执行完成并唤醒自己。
锁升级的优化是针对于不同同步场景进行的优化,在不存在锁竞争的时候进入同步方法/代码块则使用偏向锁,存在竞争时升级为轻量级锁,轻量级锁采用的是自旋锁,如果同步方法/代码块执行时间很短的话,采用轻量级锁虽然会占用cpu资源但是相对比使用重量级锁还是更高效的,但是如果同步方法/代码块执行时间很长,那么使用轻量级锁自旋带来的性能消耗就比使用重量级锁更严重,这时候就需要升级为重量级锁。
评论区