HotSpot虚拟机实现垃圾收集算法时,必须对算法的执行效率有严格的考量,才能保证虚拟机高效运行。

枚举根节点

  • 可作为GC Roots的节点
    • 全局性的引用
      • 常量或类静态属性
    • 执行上下文
      • 栈帧中的本地变量表

可达性分析必须在一个能确保一致性的快照中进行,这导致GC进行时必须停顿所有Java执行线程(Sun称为“Stop-The-World”)。CMS收集器中,枚举根节点时也是必须要停顿的。

一致性:整个分析期间整个执行系统看起来就像被冻结在某个时间点上,不可能出现分析过程中对象引用关系还在不断变化的情况,该点不满足就无法保证分析结果准确性

由于目前的主流Java虚拟机使用的都是精准式GC,在HotSpot的实现中,是使用一组称为OopMap的数据结构来达到这个目的的,在类加载完成的时候,HotSpot就把对象内对应偏移量上是什么类型的数据计算出来,在JIT编译过程中,也会在特定的位置记录下栈和寄存器中哪些位置是引用。这样,GC在扫描时就可以直接得知这些信息了。

准确式GC:就是让JVM知道内存中某位置数据的类型什么。比如当前内存位置中的数据究竟是一个整型变量还是一个引用类型。这样JVM可以很快确定所有引用类型的位置,从而更有针对性的进行GC roots枚举。

安全点

概述

HotSpot没有为每条指令都生成OopMap,只有在“特定的位置”记录了这些信息,这些位置称为安全点(Safepoint)

程序执行时并非在所有地方都能停顿下来开始GC,只有在到达安全点时才能暂停。

选择标准

是否具有让程序长时间执行的特征为标准

安全点的选定既不能太少,让GC等待时间太长,也不能太多,过分增大运行时的负荷。

每条指令执行的时间都非常短暂,程序不太可能因为指令流长度太长而过长时间运行。

  • 长时间执行的特征就是指令序列复用
    • 方法调用
    • 循环跳转
    • 异常跳转

具有这些功能的指令才会产生Safepoint

  • 如何在GC发生时让所有线程(不包括执行JNI调用的线程)都运行到最近的安全点上再停顿下来?
    • 抢先式中断Preemptive Suspension
    • 主动式中断Voluntary Suspension

抢先式中断

不需要线程的执行代码主动配合,在GC发生时,首先把所有线程全部中断,如果发现有线程不在安全点上,就恢复线程,直到运行至安全点再中断。

几乎没有虚拟机实现采用抢先式中断

主动式中断

当GC需要中断线程时,不直接对线程操作,仅仅设置一个标志,各个线程执行时主动去轮训这个标志,发现中断标志为真时就主动中断挂起。

轮询标志的地方和安全点是重合的,另外再加上创建对象需要分配内存的地方。

1
0x01b6d62d: test   %eax,0x160100      ;   {poll}

test:HotSpot生成的轮询指令

当需要线程暂停时,虚拟机把0x160100的内存页设置为不可读,线程执行到test指令时就会产生一个自陷异常信号,在预先注册的处理器中暂停线程实现等待,这样一条汇编指令便完成安全点轮询和触发线程中断。

安全区域

背景

对于程序不执行时,线程须发响应JVM的中断请求,运行至安全的地方中断挂起,JVM也不太可能等待线程重新运行,此时就需要安全区域来解决。

程序不执行:

没有分配CPU时间,如:线程处于Sleep或Blocked状态

概述

Safe Region,在一段代码片段之中,引用关系不会发生变化。在这个区域中的任意地方开始GC都是安全的。

被扩展后的Safepoint

执行过程

在线程执行到Safe Region中的代码时

  • 标志自己已经进入了Safe Region
    • 在这段时间里JVM要发起GC时,就不用管标志自己为Safe Region状态的线程了

在线程要离开Safe Region时

  • 检查系统是否已经完成了根节点枚举(或时整个GC过程)
    • 是:线程继续执行
    • 否:必须等待至收到可以安全离开Safe Region的信号

参考资料

聊聊JVM