深入探究 JVM | Safepoint 及 GC 的触发条件

最近看JVM看得火热。。在这里我们来总结一下HotSpot JVM里GC Safepoint以及触发GC的条件相关的一些知识。

GC Safepoint

如果要触发一次GC,那么JVM中所有Java线程都必须到达GC Safepoint。

JVM只会在特定位置放置safepoint,比如:

  • 内存分配的地方(allocation,即new一个新对象的时候)
  • 长时间执行区块结束的时刻(如方法调用,循环跳转等)

之所以只在特定的位置放置safepoint,是因为OopMap要占用空间,如果设太多safepoint那么占用空间会太大;再者,safepoint会影响优化,如果某个无用的值处设置了safepoint,那么JIT就无法优化掉这些无用变量,这会影响性能。

HotSpot JVM在通过JIT编译时,会在所有方法返回之前以及循环跳转、异常跳转之前放置Safepoint,并且在每个Safepoint都生成一些信息存储哪些地方是引用(OopMap),以便JVM能找到需要的引用。

那么如何确保GC时所有线程都到达GC Safepoint呢?有两种方法:抢占式中断(Preemptive Suspension)和主动式中断(Voluntary Suspension)。

抢占式中断不需要线程的执行代码去主动配合,当触发GC时,JVM会中断所有线程,然后依次检查每个线程中断的位置是否为Safepoint,如果不是则恢复线程,让它执行至Safepoint再进行终端。

大部分JVM实现(如HotSpot JVM)都是采用主动式中断,即GC需要中断线程的时候,它仅仅简单地设个标志,执行线程会主动轮询这个标志位,如果标志位就绪的话就自行中断。Polling Point与Safepoint是重合的。主动式中断的思想是一种hand-shacking protocol的思想。

通过对Safepoint的研究,我们了解到了GC触发的时刻主要是在new一个新的对象或者在循环跳转或方法返回之前

【扩展】Safepoint有好几种,比如还有deoptimization safepoint之类的,作用不同。

Safe-Region

只有GC Safepoint是不足的,因为我们发现,有一种情况,线程无法响应JVM的中断请求,也无法去轮询标志位:

  • 线程处于阻塞或等待状态

对于这种情况,引入了safe-region的概念。

Safe-Region是指在代码片段中,引用关系不会发生变化,因此GC可以随心所欲地在任何地方执行。在线程执行到Safe Region里面的代码时,首先标识自己已经进入了Safe Region,那样当这段时间里JVM要发起GC,就不用管标识自己为Safe Region状态的线程了。在线程要离开Safe Region时,它要检查系统是否已经完成了根节点枚举(或者是整个GC过程),如果完成了,那线程就继续执行,否则它就必须等待直到收到可以安全离开Safe Region的信号为止。

另外,当一个线程在执行native方法时,由于此时该线程在执行JVM管理之外的代码,不能对JVM的执行状态做任何修改,因而JVM要进入safepoint不需要关心它。所以也可以把正在执行native函数的线程看作“已经进入了safepoint”,或者把这种情况叫做“在safe-region里”。


参考资料

文章目录
  1. 1. GC Safepoint
  2. 2. Safe-Region
  3. 3. 参考资料