jvm探索揭秘过程

Posted 知行一生

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了jvm探索揭秘过程相关的知识,希望对你有一定的参考价值。

最近在研究多线程,期间写了这么一段代码

起先我是想用原子类去测试线程安全,毫无疑问cas操作是安全的,但是由于这里计算很耗时所以我就让他休眠1000ms.


然而奇妙的事情发生了,按照代码来看,主线程休眠1s后就会输出结果,但是实际情况确实主线程一直等待t1,t2执行结束才继续执行。


出于好奇心,我将休眠的时间调整到了100ms,在运行一下,这次运行就正常了,主线程并不会等待子线程运行结束之后在输出。


so amazing ! ! !


于是我猜测肯定有什么机制导致了这样的现象,难道是cas?


为了验证我将代码改造了一下

jvm探索揭秘过程


但是当我运行起来就想起来了,这肯定会直接输出,果然结果如我所料;

可见并不是cas引起的总线风暴问题。

总线风暴:volatile 和CAS 的操作导致BUS总线缓存一致性流量激增所造成


那么...根据原子类的设计,我们只能将瞄头进一步对准volatile字段了。

于是乎第三版本产生了

jvm探索揭秘过程

volatile 指定可见性,防止指令重排序。让 sum 的修改对所有线程都可见

这一次没有失望,果然通过volatile指定对线程的可见性之后,main的深度睡眠问题又一次的复现了。


于是我想监控一下运行期间发生了什么,通过jvisualvm看一下

jvm探索揭秘过程

通过监控看到运行期间cpu十分繁忙,我电脑是6核12线程的,cpu运行期间利用率在20%左右


dump看一下里面有这么一段:

"VM Thread" os_prio=31 tid=0x00007fca6a018000 nid=0x4c03 runnable "GC task thread#0 (ParallelGC)" os_prio=31 tid=0x00007fca6b009000 nid=0x1e07 runnable "GC task thread#1 (ParallelGC)" os_prio=31 tid=0x00007fca69009000 nid=0x2203 runnable "GC task thread#2 (ParallelGC)" os_prio=31 tid=0x00007fca6b009800 nid=0x2003 runnable "GC task thread#3 (ParallelGC)" os_prio=31 tid=0x00007fca68009000 nid=0x2a03 runnable "GC task thread#4 (ParallelGC)" os_prio=31 tid=0x00007fca6800d000 nid=0x2b03 runnable "GC task thread#5 (ParallelGC)" os_prio=31 tid=0x00007fca6800e000 nid=0x2c03 runnable "GC task thread#6 (ParallelGC)" os_prio=31 tid=0x00007fca6800e800 nid=0x2d03 runnable "GC task thread#7 (ParallelGC)" os_prio=31 tid=0x00007fca6b00a000 nid=0x2e03 runnable "GC task thread#8 (ParallelGC)" os_prio=31 tid=0x00007fca6800f000 nid=0x4f03 runnable "GC task thread#9 (ParallelGC)" os_prio=31 tid=0x00007fca6b00a800 nid=0x4d03 runnable "VM Periodic Task Thread" os_prio=31 tid=0x00007fca6a019000 nid=0xa703 waiting on condition


触发了gc??感觉越陷越深,代码中并没有频繁的创建对象,为何会引起gc,这使我更加疑惑了。


百般无奈之下我只能去stackoverflow上提个question了:

期间看了这么一篇文章:

https://zhuanlan.zhihu.com/p/286110609

作者详细的介绍了由于gc导致的stw使得线程需要等待一段时间,HotSpot定位GC Roots时通过引入Safepoint(安全点)来提高垃圾收集效率,其中提到:长时间执行的特征会被设置为安全点毫无疑问我们程序中的循环是十分耗时的。章中还有提到安全区域用来解决线程处于sleep状态时无法相应虚拟机的终端请求,不能再走到安全的地方去中断挂起自己的问题。更多的内容大家可以看下文末的连接。


作者的例子中是由于大量创建对象导致gc触发的main线程等待子线程到达安全点之后执行的效果,虽然和我的场景不完全一样,因为上面的例子中很显然我们是没有触发gc的。


在5小时候我终于等到了stackoverflow的回答,这个回答惊艳到了我,不多bb,马上分享一下作者的说法:

作者的回答从背景,到我写的实例产生的问题,以及具体的解决方案一一道来。

下面是作者的一段回答

HotSpot JVM不仅将安全点用于GC,而且还将其用于许多其他操作特别是,当有清理任务要执行时,它会定期停止Java线程周期由-XX:GuaranteedSafepointInterval默认为1000毫秒的选项控制


您的示例中发生了什么

  1. 您将启动两个较长的不间断循环(内部没有安全点检查)。

  2. 主线程进入睡眠状态1秒钟。

  3. 在1000 ms(GuaranteedSafepointInterval)之后,JVM尝试在安全点停止

    Java线程以进行定期清理,但是直到计数循环完成后才能执行此操作。

  4. Thread.sleep 方法从本机返回,发现安全点操作正在进行中,并挂起,直到操作结束。

此时,主线程正在等待循环完成-正是您所观察到的。

当您将睡眠时间更改为100 ms时,保证的安全点将在Thread.sleep返回后发生,因此该方法不会被阻止。

或者,如果您使1000 ms-XX:GuaranteedSafepointInterval=2000处于睡眠状态,但是增加睡眠时间,则主线程也不必等待。


在此疑惑解除,当sleep1000ms的时候,jvm尝试在安全点停止Java线程以进行定期清理,此时安全点需要一直等待子线程都执行结束后才能继续执行。




总线风暴
https://cloud.tencent.com/developer/article/1707875
伪共享:
https://www.cnblogs.com/cyfonly/p/5800758.html
jvm全局观:
https://zhuanlan.zhihu.com/p/286110609
stockoverflow
https://stackoverflow.com/questions/67068057/the-main-thread-exceeds-the-set-sleep-time

以上是关于jvm探索揭秘过程的主要内容,如果未能解决你的问题,请参考以下文章

JAVA-大白话探索JVM-类加载过程

搞定Jvm面试 Java 内存区域揭秘附常见面试题解析

Java开发经典实战!java代码编译过程

JAVA-大白话探索JVM-运行时内存

深入JVM--探索Java虚拟机的类加载机制

python 用于数据探索的Python代码片段(例如,在数据科学项目中)