垃圾回收算法通常都有个阶段要暂停所有线程对内存对象引用关系网络的更新,这个机制称为Safepoint。所有线程达到Safepoint所需要的时间对垃圾回收的总体暂停时间有很大影响。
要暂停一个线程的执行有两个途径,一个是协作式的(非抢占式),一个是抢占式的。用户态的线程可以被处于核心态的时钟中断抢占(从而完成调度)。但是用户态的线程怎么能被同样处于用户态的垃圾回收线程抢占呢?所有通常只能采用协作式的方式,让线程自己暂停。
基本的思想是设置一个全局变量作为暂停标记,每个线程时不时的去检查一下这个标记看是不是需要暂停。当然越快停下越好,但是也不能时时刻刻检查浪费CPU。
JVM运行的几种类型的代码有自己的检查这个标记的方式:
(1)对于正常解释执行的Java字节码。此时解析器会在执行每条指令前做检查(可以做些优化)。
(2)JNI代码。如果JNI代码不和JVM交互,实际上可以不用暂停继续执行,但是一旦调用JVM代码就会检查暂停标记。
(3)JIT编译出来的机器码。此时会在编译的代码中插入检查暂停标记的代码,比如在方法调用或者循环语句中。
HotSpot JVM以一种比较特殊的方式来检查暂停标记。这个标记放在一个特殊的内存位置。当需要暂停的时候,JVM把这块内存unmap掉,从而触发中断,JVM处理这个中断,然后把线程挂起。
http://blog.ragozin.info/2012/10/safepoints-in-hotspot-jvm.htmlGo的做法是类似的,一开始只能在函数调用时检查暂停标记,后来增加了在循环中检查。
https://github.com/golang/go/issues/10958但是Go对于Safepoint检查带来的性能影响不太满意,所以计划实现抢占式方式。 其实“用户态的线程怎么能被同样处于用户态的垃圾回收线程抢占”还是有可能的,可以利用信号来实现,具体怎么做还不是太清楚。
https://github.com/golang/go/issues/24543