死磕synchronized三:系统剖析延迟偏向篇二

375 阅读6分钟

哈喽,大家好,我是江湖人送外号[道格牙]的子牙老师。

近期准备写一个专栏:从Hotspot源码角度剖析synchronized。前前后后大概有10篇,会全网发,写完后整理成电子书放公众号供大家下载。对本专栏感兴趣的、希望彻彻底底学明白synchronized的小伙伴可以关注一波。电子书整理好了会通过公众号群发告知大家。我的公众号:硬核子牙

市面上关于synchronized的资料已经很多了,我这个专栏跟那些资料有啥差别呢:

  1. 更系统。市面上目前虽然资料众多,但都是零散的。有些资料讲得东西甚至是相互冲突的,都不知道信谁的。我准备从Java层面到JVM层面到操作系统层面系统的去分析用synchronized后呈现的每个现象背后的本质。synchronized很多知识点市面上是没有资料讲的,我给它补上。
  2. 更接近真相。市面上的很多资料,有的是基于字节码解释器那块的代码yy出来的,有的是东拼西凑整合出来的,各个说的都像真的一样,把看的人搞蒙圈了。我准备从模板解释器代码入手,单步调试着研究,有些不确定的自己写代码去证明,争取分享给大家的都是本来如此的知识。不确定的地方我会标注出来。
  3. 授人以鱼不如授人以渔。我会以大家学完后能够手写出synchronized的标准来设计这个专栏。因为从我自己研究的角度来说,抛开语言的障碍,synchronized的每种机制如果让你实现你手足无措,那你还是没有真正地理解synchronized。言外之意就是你不一定要去手写,但是你在脑海中回想,比如CAS、锁膨胀、锁对象加锁解锁……你大概知道代码是怎么写的。

关于分析偏向锁延迟策略:

  1. 什么是延迟偏向
  2. 为什么需要延迟偏向
  3. 延迟偏向机制是怎样的
  4. 延迟偏向对锁膨胀的影响及证明
  5. 从Hotspot源码角度证明

上篇已经分享了绝大部分内容,本篇就把剩下的补上。本篇文章是从Hotspot源码角度分析延迟偏向机制:

  1. 新创建的对象的锁是是如何被延迟偏向影响的
  2. 延迟偏向之前加载的类的初始锁是什么锁
  3. 延迟偏向之后加载的类是无锁还是偏向锁

对象的初始锁

新创建的对象的初始锁是如何确定的,直接看Hotspot源码

JVM创建对象的步骤是先申请内存,然后对这块内存进行数据填充。数据填充包括设置对象头。这个方法就是在数据填充阶段调用的。代码逻辑很简单,如果启用了偏向锁,则将这个oop对应的klass的属性property_header作为初始锁填充进去。如果没有启用偏向锁,则生成一个无锁作为初始锁填充进去。

免得有小伙伴没看过我之前的文章或视频,不知道oop与klass,做个简单的解释:Hotspot主要部分是由C++编写的,oop就是Java对象对应的C++实例,klass就是Java类对应的C++实例。

正常情况下偏向锁都是开启的,那创建的对象的初始锁最终受限于延迟偏向,再细化一点说是受限于创建对象基于的这个klass是在延迟偏向之前加载的还是之后。接下来咱们就看看下面三种情况底层是如何实现的:

  1. 基于延迟偏向之前加载的类,在延迟偏向之前创建的对象初始锁是无锁
  2. 基于延迟偏向之前加载的类,在延迟偏向之后创建的对象是偏向锁
  3. 基于延迟偏向之后加载的类,创建的对象都是偏向锁

延迟偏向之前

首先,延迟偏向之前所有加载的类默认是无锁,所以延迟偏向之前所有创建的对象都是无锁,核心代码如下

那延迟偏向之前加载的类,延迟偏向之后创建的对象为什么是偏向锁呢?看代码

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源码找线索,终于找到了核心代码

至此,延迟偏向相关的知识点就全部给大家讲完了。

系列文章

1、JVM如何执行synchronized修饰的方法

2、死磕synchronized二:系统剖析延迟偏向篇一

推荐阅读

1、你是不是想问,那些技术大牛是如何练成的?我来告诉你

2、JMM到底如何理解?JMM与MESI到底有没有关系?

3、从hotspot源码层面剖析Java的多态实现原理

结语

其实技术这个行业真的不难,如果有人带,打底子1-2年,沉淀2-3年,足矣。我自己一步步探索,走得还算顺利,大概花了七年时间。

给大家看看我之前写的一些项目,证明下我不是在吹牛。我不喜欢吹牛,我也不喜欢水课水文章,内心接受不了。