死磕synchronized三:系统剖析延迟偏向篇二原创
哈喽,我是子牙。十余年技术生涯,一路披荆斩棘从技术小白到技术总监到JVM专家到创业。技术栈如汇编、C语言、C++、Windows内核、Linux内核。特别喜欢研究虚拟机底层实现,对JVM有深入研究。分享的文章偏硬核,很硬的那种。
手撸过JVM、内存池、垃圾回收算法、synchronized、线程池、NIO…
关于分析偏向锁延迟策略:
-
什么是延迟偏向
-
为什么需要延迟偏向
-
延迟偏向机制是怎样的
-
延迟偏向对锁膨胀的影响及证明
-
从Hotspot源码角度证明
上篇已经分享了绝大部分内容,本篇就把剩下的补上。本篇文章是从Hotspot源码角度分析延迟偏向机制:
-
新创建的对象的锁是是如何被延迟偏向影响的
-
延迟偏向之前加载的类的初始锁是什么锁
-
延迟偏向之后加载的类是无锁还是偏向锁
新创建的对象的初始锁是如何确定的,直接看Hotspot源码
JVM创建对象的步骤是先申请内存,然后对这块内存进行数据填充。数据填充包括设置对象头。这个方法就是在数据填充阶段调用的。代码逻辑很简单,如果启用了偏向锁,则将这个oop对应的klass的属性property_header作为初始锁填充进去。如果没有启用偏向锁,则生成一个无锁作为初始锁填充进去。
免得有小伙伴没看过我之前的文章或视频,不知道oop与klass,做个简单的解释:Hotspot主要部分是由C++编写的,oop就是Java对象对应的C++实例,klass就是Java类对应的C++实例。
正常情况下偏向锁都是开启的,那创建的对象的初始锁最终受限于延迟偏向,再细化一点说是受限于创建对象基于的这个klass是在延迟偏向之前加载的还是之后。接下来咱们就看看下面三种情况底层是如何实现的:
-
基于延迟偏向之前加载的类,在延迟偏向之前创建的对象初始锁是无锁
-
基于延迟偏向之前加载的类,在延迟偏向之后创建的对象是偏向锁
-
基于延迟偏向之后加载的类,创建的对象都是偏向锁
首先,延迟偏向之前所有加载的类默认是无锁,所以延迟偏向之前所有创建的对象都是无锁,核心代码如下
那延迟偏向之前加载的类,延迟偏向之后创建的对象为什么是偏向锁呢?看代码
JVM在启动的时候调用了BiasedLocking::init,这个方法会创建一个VM_Operation塞入VMThread的任务队列中,经历偏向延迟后才会被执行。执行的内容就是遍历之前加载的所有的类,将它们的属性property_header改为偏向锁,即biased_locking_prototype。
VMThread在执行这个方法期间,就是还在执行classes_do方法,还未将_biased_locking_enabled设为TRUE,这个时候发生了类加载,那这时候加载的类生成的对象就永远是无锁状态了。为了这个问题我还特意梳理了下代码,发现JVM为了避免这个问题,执行这个方法之前是会启用安全点创造STW环境的。
SafepointSynchronize::begin();
evaluate_operation(_cur_vm_operation);
新加载的类默认都是无锁,如果是延迟偏向之前加载的类,会在延迟偏向之后,由VMThread将其改为偏向锁,从而影响基于这个类创建的对象的锁状态。那基于延迟偏向之后加载的类创建的对象是偏向锁,是为什么呢?
第一种可能是:加载的类是无锁,已经过了延迟偏向时间了,就不会有其他程序来改类的锁状态,所以是在创建对象填充的时候修改的。这个其实从上面介绍新创建的对象的初始化填充就已经可以正式了。所以这种可能pass。
所以只剩这种可能,加载的类的锁在加载期间被改掉了,改成了偏向锁,按照这个分析我单步调试Hotspot源码找线索,终于找到了核心代码
至此,延迟偏向相关的知识点就全部给大家讲完了。
我有话说
我是子牙。十余年技术生涯,一路披荆斩棘从小白到技术总监到大厂中间件到创业。技术栈如汇编、C语言、C++、Windows内核、Linux内核及特别喜欢研究虚拟机底层实现,对JVM有深入研究。分享的文章偏硬核,很硬的那种。不考虑交个朋友吗?关注硬核子牙: