如果面试官让你分析类初始化阶段的死锁现象原创
哈喽,大家好,我是江湖人送外号[道格牙]的子牙老师。
准备写两篇文章透彻剖析下类的初始化阶段及初始化阶段的死锁问题:
-
类的初始化做什么
-
JVM底层是如何实现类的初始化的
-
为什么会出现死锁问题
-
怎么解释死锁问题
-
如果证明你对死锁的判断是正确的
-
我是如何论证的
会由浅入深,循序渐进展开。今天是第一篇,难度偏认知层面。读起来应该会很轻松愉快。
clinit
类初始化阶段做什么?其实很简单,执行clinit方法。这个方法哪里来的?你的Java代码中只要有静态属性或者是静态代码段,在编译的时候就会自动生成这个方法。
代码中有静态属性,如图
代码中有静态方法,如图
这段代码有两个地方需要注意下:1、静态代码块前可以写static,也可以不写;2、如果代码中有多个静态代码块,编译系统会合并成一个,合并后的代码顺序跟静态代码块的先后顺序保存一致。
死锁现象
国际惯例,上代码
这段代码运行起来的结果,如图
代码永远不会结束,其实在JVM层面,是发生了死锁。
检测死锁
一提到死锁,我们会马上想起JVM提供的工具:jconsole、visualvm…期待的结果是
但是很遗憾,目前没有工具能够检测出初始化阶段发生的死锁问题(我准备尝试写一个)。
为什么jconsole检测不到呢?看图
线程根据执行代码进入的空间来分,有这四种基本状态,目前能看得到的工具,或者是JVM开放的API,只能做到检测出thread_in_Java这种状态的线程死锁问题。
所以这个问题只能通过阅读Hotspot源码找答案。所以可以推测出面试官问你这个问题,就是看你有没有这个能力,或者是这个习惯,通过阅读Hotspot源码找答案。
初始化流程
类的初始化阶段,对应Hotspot源码:InstanceKlass::initialize_impl。考虑到很多小伙伴不想看C++代码,我用伪代码把意思表达到
讲两个预备知识:
-
看这段代码的时候要区别正在执行初始化的线程及其他线程。假设正在执行初始化的线程为T1,又进来一个线程T2执行初始化
-
Hotspot源码中此处有两个状态对理解代码至关重要:being_initialized(正在执行初始化)、fully_initialized(完成初始化)
问题的答案已经很清晰了:
-
第一个线程执行new指令,触发加载类A,然后执行A的初始化方法clinit,clinit方法中又执行到new指令,触发加载类B,并执行类B的初始化方法clinit
-
第二个线程触发加载类B,在类B的clinit方法中又触发加载类A
-
死锁的原因就是线程一跟线程二都进入了wait,也就是初始化流程的Step 2
-
其实这个问题存在时间差,如果某个线程跑得足够快,完成了初始化,死锁就不会发生。所以如果你的程序出现有时候卡着不动,有时候又是正常的,不妨大胆猜测有可能是发生了初始化阶段死锁。正常来讲,出现死锁的频率更高,我测试了很多次,理想情况发生的次数还是很少很少的
然,目前得出的答案是从理论层面分析出来的,那事实是不是如此呢?如何证明呢?下篇文章分享。