无意间在某公众号中发现了关于RocketMq源码的的一张图片,看到Thread.sleep(0)可以prevent gc 感到十分震惊,想探究其中的奥秘,于是阅读了相关文章之后似乎有了一点感觉,遂记录下来,顺便希望通过这篇文章能够让我延续记录的习惯。
在了解这行代码之前,首先需要大概了解java gc的stw(stop the world)以及safepoint两个知识点。大概就是如果当前程序进入safepoint后会检测stw标志位,如果stw为true则需要进行一次gc后再继续程序。
counted loop 可数循环
在counted loop(可数循环)中,jvm是不会进入safepoint的。简单来说 如果你的当前循环是counted loop,那么jvm会在所有循环结束之后在进入safepoint完成gc,此时循环产生的垃圾数量可能会让此次gc时间更长,从而导致性能下降。
那什么是counted loop?
-------《深入理解JVM虚拟机(第三版)》:
从中可以看到,如果使用int或更小范围类型作为索引值的话是不会放置安全点的,这种循环成为counted loop可数循环,那我们很容易就知道不可数循环是通过使用long及long以上的类型作为索引值的循环。
所以由此我们可以知道,在源码中如果不手动进入safepoint 那么只有在整个循环完毕才会进入safepoint检查stw标志位,那么此时经过循环之后需要gc的垃圾可能会很多,可能会导致gc时间过长而影响性能。
那么 为什么 Thread.sleep(0)可以阻止这种情况?
本图摘选自知乎 https://www.zhihu.com/question/29268019/answer/43762165,作者:RednaxelaFX
可以看到在调用jni(java native interface) 的时候会进入safepoint
为了证实这一观点 我们去看看cpp的源码
https://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/tip/src/share/vm/runtime/safepoint.cpp
源码确认无疑 那么我们再去看看Thread.sleep()是否是jni就能破案。
打开sleep源码 前面的native给了我们答案。
至此,这几行代码的作用我们已经十分明了,把循环结束执行的长时gc切割为循环过程中手动进入的小gc,从而减少gc对系统性能的影响。