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的信号