《深入理解 Java 虚拟机》- 笔记 - HotSpot 的垃圾收集算法实现

枚举根节点


从可达性分析中从 GC Roots 节点找引用链这个操作为例,可以作为 GC Roots 的节点主要有:全局性的引用(如常量、类静态属性),执行上下文(如栈帧中的本地变量表);有些应用的方法区多达上百兆,如果要逐个检查引用将会消耗很多时间。

可达性分析会导致 GC 停顿,因为可达性分析期间整个执行系统被冻结在某个时间点上,期间对象引用关系不会发生变化,将使所有 Java 执行线程停顿。

目前主流的 Java 虚拟机使用的都是准确式 GC,当执行系统停顿下来后,虚拟机是有办法直接得知哪些地方放着对象的引用,所以不需要一个不漏的检查完所有执行上下文和全局的引用位置。

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

安全点


OopMap 内容变化的指令非常多,如果为每一条指令都生成对应的 OopMap,需要大量的额外空间,GC 的成本将会变得很高。

实际上 HotSpot 没有为每条指令都生成 OopMap,只在“特定的位置”记录了这些信息,这些位置称为安全点(Safepoint),程序只有到达安全点才停顿下来开始 GC。

安全点的选定基本上是以程序“是否具有让程序长时间执行的特征”为标准进行选定的——因为每条指令的执行时间都非常短暂,程序不太可能因为指令流长度太长这个原因而过长时间运行,“长时间执行”的最明显特征就是指令序列的复用,如:方法调用、循环跳转、异常跳转等,所以具有这些功能的指令才会产生安全点。

如何在 GC 发生时然所有线程(不包括执行 JNI 调用的线程)都“跑”到最近的安全点上再停顿下来,这里有两种方案:抢先式中断(Preemptive Suspension)和主动式中断(Voluntary Suspension)。

  • 抢先式中断不需要线程的执行代码主动去配合,在 GC 发生时,首先把所有线程全部中断,如果发现有线程中断的地方不在安全点上,就恢复线程,让他“跑”到安全点上。(现在几乎没有虚拟机实现采用抢先式中断来暂停线程来响应 GC 事件)。

  • 主动式中断是当 GC 需要中断线程的时候,不直接对线程操作,仅仅简单地设置一个标志,各个线程执行时主动去轮询这个标志,发现中断标志位为真时就自己中断挂起,轮序标志的地方和安全点是重合的,另外再加上创建对象需要分配内存的地方。

安全区域


安全点机制保证了程序执行时,在不太长的时间内就能遇到可进入 GC 的安全点。如果线程处于 Sleep 或 Blocked 状态等不执行的时候,无法响应 JVM 的中断请求,无法“走”到安全的地方中断挂起。对于这种情况,就需要安全区域(Safe Region)来解决。

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

在程序执行到安全区域中的代码时,首先标识自己已经进入了安全区域。当在这段时间 JVM 发起 GC 时,就不用管那些标识为安全区域状态的线程了。在线程要离开安全区域时,它要检查系统是否已经完成根节点枚举。如果完成,那线程就继续执行,否则它就必须等待直到收到可以安全离开安全点的信号为止。

你可能感兴趣的:(jvm)