相关文章:
之前我们了解了对象存活判定算法以及垃圾回收算法,而在 HotSpot 虚拟机上实现这些算法时,必须对算法的执行效率有严格的考量,才能保证虚拟机高效运行
一、枚举根节点
-
在可达性算法中,可作为 GC Roots 的节点主要在全局性的引用 (例如常量或类静态属性) 与执行上下文 (例如栈帧中的本地变量) 中,如果逐个检查这里面的引用,那么必然会消耗很多时间
-
另外,可达性分析对执行时间的敏感还体现在 GC 停顿上,因为这项分析工作必须在一个能确保一致性的快照中进行:这里 “一致性” 指的是在整个分析期间整个执行系统看起来就像被冻结在某个时间点上,不可以出现分析过程中对象引用关系还在不断变化的情况,这点不满足的话分析结果准确性就无法得到保证,这点是导致 GC 进行时必须停顿所有 Java 执行线程的其中一个重要原因 (Stop The World)
-
目前主流的 Java 虚拟机使用的都是准确性 GC (Exact VM 虚拟机采用准确式内存管理,即虚拟机可以知道内存中某个位置的数据具体是什么类型),所以当执行系统停顿下来的时候,并不需要去检查所有执行上下文和全局的引用位置,虚拟机可以直接得知哪些地方存放着对象引用
-
在 HotSpot 虚拟机实现中,通过使用一组称为 OopMap 的数据结构来达到这个目的,在类加载完成的时候,HotSpot 就把对象内什么偏移量上是什么类型的数据计算出来,在 JIT 编译过程中,也会在特定的位置记录下栈和寄存器中哪些位置是引用,这样 GC 在扫描时就可以直接得知这些消息了
二、安全点
-
在 HotSpot 虚拟机实现中,只会在 “特定的位置” 记录对象引用信息,这些位置称为安全点 (Safepoint),即程序执行时并非在所有地方都能停顿下来开始 GC,只有在到达安全点时才能暂停。安全点的选定既不能太少也不能太多,太少会导致 GC 等待时间过长;太多会导致程序运行时负荷增大
-
安全点的选定基本上是程序以 “是否具有让程序长时间执行的特性” 为标准进行的,“长时间执行” 的最明显特征就是指令序列的复用,例如:方法调用、循环跳转、异常跳转等,具有这些功能的指令才会产生安全点
-
当要进行 GC 时,需要让所有线程 (不包括执行 JNI 调用的线程) 都跑到最近的安全点再停顿下来,这里有两种方法可供选择
-
抢断式中断
-
在 GC 发生时,首先把所有线程全部中断,如果发现有线程中断的地方不在安全点,则恢复线程,让其跑到安全点上
-
抢断式中断不需要线程的执行代码去主动配合
-
-
主动式中断
-
当 GC 需要中断线程的时候,不直接对线程进行操作,仅仅是设置一个标志,各个线程执行时会去主动轮询这个标志,发现中断标志为真时就自己中断挂起
-
轮询标志的地方和安全点是重合的,另外再加上创建对象需要分配内存的地方
-
-
三、安全区域
-
当线程处于 Sleep 状态或者 Blocked 状态时,无法响应虚拟机的中断请求,跑到安全的地方去中断挂起,虚拟机也不太可能等待线程重新被分配 CPU 时间,这时候就需要用安全区 (Safe Region) 来解决
-
安全区是指在一段代码片段中,引用关系不会发生变化,在这个区域中的任意地方开始 GC 都是安全的,我们可以将安全区看作是被扩展了的安全点
-
当线程执行到安全区中的代码时,首先会标记自己已经进入了安全区,当虚拟机要发起 GC 时,就会跳过已标识为安全区状态的线程;当线程要离开安全区时,它会检查系统是否已经完成了根节点枚举 (或者是整个 GC 过程),如果完成了,那线程就会继续执行,如果没有则必须等待直到收到可以安全离开安全区的信号为止
四、归纳总结
-
Stop-the-World
-
虚拟机由于要执行 GC 而停顿了所有 Java 执行线程
-
在任何一种 GC 算法中都会发生
-
和线程是一对一的关系,即 “线程私有”
-
多数 GC 优化通过减少 Stop-the-World 发生的时间来提高程序性能
-
-
安全点
-
分析过程中对象引用关系不会发生变化的点
-
产生安全点的地方:方法调用、循环跳转、异常跳转等
-
安全点数量得适中,太少会导致 GC 等待时间过长;太多会导致增大程序运行时的负荷
-
-
安全区域
- 分析过程中对象引用关系不会发生变化的一段代码片段