上一篇介绍了对象存活的判定方法以及垃圾收集算法,HotSpot作为最主流的JVM,在算法选择上也有严格的考量,来保证虚拟机高效运行。
枚举根节点-OopMap
在可达性分析算法中,需要获取所有的GC Roots以及Reference Chain。
棘手的问题:
1.在获取GC Roots时,如果逐个遍历查找,极其消耗时间。
2.可达性分析执行时需要GC停顿,在整个分析期间整个执行系统看起来就像被冻结在某个时间点上,不可以出现分析过程中对象引用关系还在不断变化的情况。(因此GC进行时必须停顿所有Java执行线程)
主流的JVM都采用的是 准确式GC。
HotSpot 采用 一组数据结构 - OopMap 来解决问题。
在类加载完成的时候,HotSpot就会把对象内什么偏移量上是什么数据计算出来(方法区的全局引用),在JIT编译过程中,也会在特定位置记下栈和寄存器中哪些位置是引用(栈中的引用)。
这样,GC时就可以直接知道哪些地方存在对象的引用,快速完成GC Roots的枚举。
安全点(Safepoint)
引用关系会不断变化,OopMap内容也会随之变化。如果为每条指令都生成OopMap,则会需要大量额外空间。所以需要选定安全点(Safepoint),在这些特定的安全点,才会生成OopMap。程序执行时并非在所有地方都能停顿下来开始GC,只有在到达安全点时才能暂停。
安全点的选择是根据 是否会让程序长时间执行 而选定。因为Safepoint的选定不能过于频繁以至于增大运行负荷,也不能太少让GC等待时间太长。长时间执行的程序明显特征 就是 指令序列复用,如循环跳转、异常跳转等。具备这样功能的指令才会产生安全点。
这些特定的位置主要在:
- 循环的末尾
- 方法临返回前 / 调用方法的call指令后
- 可能抛异常的位置
中断(Suspension)
所以需要在GC发生时,线程(这里不包括执行JNI调用的线程)都“跑”到最近的安全点上再停顿下来。
抢先式中断(Preemptive Suspension)
在GC发生时,首先把所有线程全部中断,如果发现有线程中断的地方不在安全点上,就恢复线程,让它“跑”到安全点上。现在几乎没有虚拟机实现采用抢先式中断来暂停线程从而响应GC事件。
主动式中断(Voluntary Suspension)
当GC需要中断线程的时候,不直接对线程操作,仅设置一个标志,各个线程执行时主动轮询这个标志,发现中断标志为真时就自己中断挂起。轮询标志的地方和安全点是重合的,另外再加上创建对象需要分配内存的地方。
形象举例来说,线程就如同高速上行驶的汽车,安全点即一个个休息站,设置的一个标志点即一个推荐就近休息的闹钟,当车上的闹钟响起时,车就找到一个就近的休息站休息,即线程发生主动式中断。
安全区域
安全区域(Safe Region)是指在一段代码片段之中,引用关系不会发生变化。在这个区域中的任意地方开始GC都是安全的。
Safepoint机制保证了程序执行时,在不太长的时间内就会遇到可进入GC的Safepoint。
但是程序"不执行”(没有分配CPU时间)时,如线程处于Sleep状态或者Blocked状态,这时候线程无法响应JVM的中断请求,如果想”走“到安全的地方去中断挂起,JVM显然不太可能等待线程重新被分配CPU时间。这时候就需要安全区域。
总结
HotSpot的GC算法主要组成有:
- 用于枚举GC Roots的OopMap数据结构
- OopMap的特定记录位置安全点
- 安全点的扩展-安全区域
GC算法主要是内存回收的方法论,下一篇的主要内容是内存回收的具体实现---垃圾收集器。
网友评论