本文由 发布,转载请注明出处,如有问题请联系我们! 发布时间: 2021-05-17没有发生GC也进入了安全点?这段关于安全点的JVM源码有点意思!

加载中

沒有产生GC也进入了安全性点?这一段有关安全性点的JVM源代码有点儿意思!

文尾 JVM 思维脑图,有必须的能够 自提

熟识并发编程的你认为下边这一段编码的实行結果是如何的?

image-20210419232745211

我假如说,实行步骤是:

  1. t1 进程和 t2 进程一直实行 num 的累积实际操作
  2. 主线任务程睡眠质量 1 秒,1 秒以后醒来打印出这时的 num 值
  3. t1 进程和 t2 进程执行加 1 的实际操作,直至实行完 两亿 次累积实际操作

你赞同吗?

我的猜测看上去没有什么难题,但具体运作实际效果证实了我是错的,下边是运作动态图:

从运作动态图上能够 见到,将编码跑起来以后,却发觉具体实行結果是那样的:

1 秒以后,主线任务程并沒有立刻打印出 num,只是等 t1 和 t2 各自实行完 2 亿个累积实际操作撤出循环系统后,才会打印出 num 的值

这一結果和预期的不一样。我是根据 JDK1.8 跑的,你也能够 试一下。

怎么会那样呢?

回答是:

JVM 要想实行某一实际操作,让全部进程进到安全性点,可是 t1 和 t2 进程由于 JIT 对可数循环系统的衔接提升务必等循环系统跑完后才进到安全性点,因此 主线任务程一直再等 t1 和 t2,一拖再拖不可以輸出 num 的值。

可数循环系统:形如 for (int i = 0; i < 100000000; i ) {...}的循环系统被称作可数循环系统

简易而言便是:主线任务程在等 t1 和 t2 进程进到安全性点

这一回答的来历,why 神转截的一篇文章:《真是绝了!这段被 JVM 动了手脚的代码!》中早已说的很清晰了,这儿不会再反复论述。

该文就来源于我那时候的一个疑惑:JVM 让进程都进到安全性点究竟做了哪些鲜为人知的事儿?

发生了 GC?

难道说是发生了 GC 吗?

第一,编码里边沒有创建对象申请办理运行内存。

第二,再加上 -XX:-PrintGC 都没有打印出 GC 日志。

第三,实行 jstat 指令,根据輸出日志能够 看得出,JVM 运作期内每个运行内存地区也没有产生变化,都没有产生 GC。

因此 ,由于发生了 GC 而必须进到安全性点这类状况被清除了。

难题就变成了:沒有产生 GC,必须全部的进程都进到安全性点做什么?

安全性点日志

再加上 -XX: PrintSafepointStatistics 主要参数,让程序运行的情况下打印出安全性点的有关日志。

能够 见到,这一段编码的实行一共开展了三次进到安全性点。

在其中第二个 EnableBiasedLocking 是 JVM 延迟打开偏向锁的实际操作,这一也较为有趣,但是并不是文章内容的关键,下一次还有机会再聊。

大家重点关注的是第一个 no vm operation 实际操作。将这一段日志独立拿出来,在主要参数表明上再加上汉语表述:

汇总而言便是:

JVM 想实行 no vm operation ,这一实际操作必须进程都进到安全性点,全部期内一共有 12 个进程,已经运作的进程有 2 个,必须等候这两个进程进到安全性点,等候这 2 个进程进到安全性点并堵塞消耗了 5037 ms。

要找到这两个进程也非常简单,它并不是必须 5000 多ms才进到安全性点吗,我也再加上主要参数让进到安全性点時间超出 5000 ms的进程请求超时就可以了。

因此再加上 -XX: SafepointTimeout 和 -XX:SafepointTimeoutDelay=5000 主要参数,实行编码。

哦豁,这并不便是 t1 和 t2 进程吗。

这一結果也是预料之中的,大家的关键是这一 no vm operation 到底是个哪些实际操作?为何让主线任务程等这么多年?

源代码精准定位

这一 VM 实际操作的名称称为 no vm operation ,翻译中文便是并不是 VM 实际操作,连起來就是否 VM 实际操作的 VM 实际操作?

一个并不是 VM 实际操作的实际操作竟然也可以让全局性进到安全性点?

那究竟是什么实际操作呢?知识盲区了呀!

一顿谷歌百度,都没有寻找一个较为相信的回答。

因此,我打算看 JVM 的源代码。

在 JVM 源代码里边全局搜索 no vm operation ,发觉仅有 safepoint.cpp 有这一信息内容。

点一下去一看,果真,一下子精准定位到打印出日志的地区,就是这个 SafepointSynchronize::print_statistics() 方式 。

在其中有一句很重要的编码:

_vmop_type == -1 ? 
    "no vm operation" : 
    VM_Operation::name(sstats->_vmop_type)

这是一个三目运算:假如 _vmop_type 相当于 -1,打印出的安全性点日子实际操作种类那一栏便会輸出 no vm operation

而这一 _vmop_typen 呢,是建筑结构 SafepointStats 中的一个组员,实际的含意是开启安全性点的 VM 实际操作种类

那什么实际操作种类会将 _vmop_type 设成 -1 呢?

我还在打开安全性点方式 里边找到回答:

要不是 VM 实际操作开启的安全性点事情,这个时候便会将 _vmop_type 设成 -1。

换句话说也有别的状况还可以开启安全性点事情,让全部进程进到安全性点。

那麼,大家只必须寻找开启安全性点事情相匹配的编码就可以了。

一个个文档找很难,换一个构思,要想进到安全性点,必然要启用进到安全性点的方式 。

而进到安全性点的方式 便是 safepoint.cpp 里边的 SafepointSynchronize::begin() 方式 。

大家只必须全局性搜一下哪儿启用了这一 SafepointSynchronize::begin() 这一方式 应当就能寻找开启安全性点事情相匹配的编码。

全局搜索发觉仅有 vmThread.cpp 里边有启用,vmThread.cpp 封裝的全是 VMThread 有关的方式 。

VMThread

VMThread 是个什么呢?

VMThread 是 JVM 本身运行的一个內部进程,它关键用于融洽其他进程做到安全性点及其实行 VM 实际操作。

VM 实际操作这一定义全篇早已数次提及了,那究竟有什么实际操作是 VM 实际操作呢?

大家较为了解的 CMS 的原始标识和最后标识全是 VM 实际操作,又例如 thread dump,进程挂起来及其偏向锁的撤消这些全是 VM 实际操作。

VM 实际操作种类有很多,JVM 相匹配的源代码在 vm_operations.hpp 界定的宏 VM_OPS_DO 里边。

宏 VM_OPS_DO 里边的每一个 VM 实际操作,大部分都是有一个独立的派生类去完成。

VMThread 里边有一个 VMOperationQueue 序列,用以储放一个一个连在一起的 VM 实际操作。

VMThread 循环系统实行 VM 实际操作的方式 ,称为 VMThread::loop() 方式 。

loop() 方式 是 VMThread 的关键方式 ,该方式 持续从 VMOperationQueue 序列中获得待实行的 VM 实际操作,随后启用每一种 VM 实际操作实际的完成 evaluate() 方式 实行不一样的逻辑性。

这儿用了策略模式,VMThread 实行逻辑性是固定不动的,只承担生产调度,而每一种 VM 实际操作必须依据要求自身完成 evaluate() 方式 。

回答发生

而大家上边千辛万苦找寻的 no vm operation 缘故,就在 VMThread 的 loop() 方式 里边。

从源代码能够 见到,在 VM 实际操作为空的状况下,只需达到下列 3 个标准,也是会进到安全性点的:

  1. VMThread 处在一切正常运作情况
  2. 设计方案了进到安全性点的时间间隔
  3. SafepointALot 是不是为 true 或是是不是必须清除

程序流程一切正常运作 VMThread 毫无疑问能一切正常运作,因此 标准 1 能达到。

java -XX: UnlockDiagnosticVMOptions -XX: PrintFlagsFinal 2>&1 | grep Safepoint 指令查询 JVM 有关安全性点的默认设置主要参数,发觉 GuaranteedSafepointInterval 默认变成 1 秒,因此 标准 2 也可以达到。

针对标准 3,SafepointALot 默认设置为 false,那要想标准 3 能达到得话,务必 SafepointSynchronize::is_cleanup_needed()为 true。

点进来看它的实际完成:

根据跟踪编码,能够 发觉 SafepointSynchronize::is_cleanup_needed() 便是分辨 StubQueue 里边是不是有 stub 缓存文件。

那 StubQueue 是什么呢?stub 又是什么呢?

这涉及到 JVM 的模版编译器和c语言编译器了,因为篇数比较有限,下一次还有机会得话再次深入分析。

我用一句话归纳便是 JVM 实行期内的编译程序表述编码缓存文件

清除 stub 你能简易的了解成清除编码缓存文件

换句话说,在 JVM 一切正常运作的情况下,假如设定了进到安全性点的间距,便会隔一段时间分辨是不是有编码缓存文件要清除,如果有,会进到安全性点。

这一开启标准并不是 VM 实际操作,因此 会将 _vmop_type 设成-1,輸出日志的情况下打印出相匹配的 no vm operation,也就是大家见到的安全性点日志。

而文章开头的代码执行实际效果,主线任务程一直等待 t1 和 t2 进到安全性点,恰好是开启了这一标准。

再度认证推理

转过头来再看文章开头的编码,根据再加上 -XX:GuaranteedSafepointInterval = 0 将进到安全性点时间间隔设成 0,也就是关掉按时进到安全性点,看一下程序执行結果是如何的。

-XX:GuaranteedSafepointInterval 是确诊特性的主要参数,必须再加上-XX: UnlockDiagnosticVMOptions 主要参数开启确诊主要参数即可应用。

从运作結果上能够 见到,关掉过一段时间进到安全性点的设定以后,主线任务程睡了 1 秒后,不会再必须等候 t1 和 t2 进程循环系统实行完,睡完以后立刻就打印出了这时的 num 值。

那样的运作結果,也再一次的认证了大家的推理。

间距一秒进到安全性点的设定或是有它的功效的,我建议请别去动它。

-XX:GuaranteedSafepointInterval 是个确诊特性的主要参数,不建议网上应用。

从在网上的参考文献看来,关闭这一主要参数也是有很有可能会导致一些未知错误,实际是啥不正确因为我沒有遇上过,也不知道是真的吗。

总而言之,网上自然环境慎重一点总没有错,假如你对 JVM 最底层并不是很了解得话,我建议或是别去动它。

有意思的注解

知识要点发送到这儿就结束了,共享一个有意思的事。

在我跟踪 JVM 源代码的全过程中,我发现了撰写 StubQueue 的创作者留有了那样一段注解:

我润饰翻译一下便是:在你不可以证实你改的没什么问题的情况下,别真他妈动来动去我编码,这一段编码比你想像中厉害的多

见到沒有,这就是高手的自豪和自信心!

回过头看我呢,我平时给编码写注解的情况下,只敢在上面写:假如你见到我的编码有 BUG,不便帮我修一下,谢谢

从写注解的自豪和自信心上就能看得出来,我与高手差别有多大了。

我一定要给油,之后也可以写下那样霸气侧漏的注解!

思维脑图

我将我本人感觉关键的 JVM 知识要点,依照自身了解构思梳理变成一个思维脑图。

有必须的能够 自提就可以了,假如照片被服务平台缩小了,你能公众号后台回 JVM 获得高清图。

必须注重的是,这是我梳理的知识要点,里边的专业知识并并不是我原創的。

也没有造就专业知识,仅仅共享自身怎样学习和了解专业知识。

思维脑图的制做参考了很多的书本和blog,包含但不限于《深入理解 Java 虚拟机》、美团技术精英团队文章内容、阿里技术精英团队文章内容、R 大的文章内容、寒泉子极大地调优文章内容。

好啦,今日的文章内容就告一段落了。

我是 CoderW,一个有时喜爱爱钻牛角尖的程序猿,大家下一期再见了!

image-20210131205854199

评论(0条)

刀客源码 游客评论