HotSpot的算法实现细节
1. 根节点枚举
所有收集器在根节点枚举时都必须暂停用户线程。
当用户线程停顿下来以后,并不需要一个不漏地检查完所有执行上下文和全局引用位置。在HotSpot的解决方案中,使用一组称为OopMap的数据结构来直接得到哪些地方存放着对象的引用。一旦类加载动作完成,HotSpot就会把对象内什么偏移量上是什么类型的数据取出来,在即时编译的过程中,也会在特定的位置记录下栈里和寄存器里哪些位置是引用。于是,收集器在扫描时可以直接得到这些信息,并不需要一个不漏地从方法区等GC Roots开始查找。
2.安全点
HotSpot不用为每一条指令都生成对应的OopMap,只是在“特定的位置”记录了这些信息,这些位置就被称为安全点。
对于安全点,在垃圾收集发生时让所有线程都跑到最近的安全点,然后停顿下来。有两种方法:1.抢先式中断。抢先式中断在垃圾收集发生时,系统首先把所有的用户线程全部中断,如果有用户线程不在安全点上,就恢复这条线程,让它一会再重新中断,直到跑到安全点上;2.主动式中断。主动式中断简单地设置一个标志位,各个线程执行过程时会不停地主动去轮询这个标志,一旦发现中断标志为真时就自己在最近的安全点上主动中断挂起。轮询标志的地方和安全点重合,还要加上所有创建对象和其他需要在java堆上分配内存的地方,这是为了检查即将要发生垃圾收集,避免没有足够内存分配新对象。
3.安全区域
安全区域是为了处理程序不执行时,无法响应中断,最终到达安全点的问题。
安全区域:确保在某一段代码片段中,引用关系不会发生变化。因此在安全区域中任意地方开始回收垃圾都是安全的。
做法:用户线程在安全区域里面的代码时,会标识自己进入了安全区域,那么这个时候虚拟机垃圾回收时就不必管这些已经声明了自己在安全区域内的线程。当线程要离开安全区域时,它要检查虚拟机是否完成了根节点枚举,如果完成了,当没有事发生,继续执行;否则一直等到,知道收到可以离开安全区域的信号。
4.记忆集与卡表
记忆集:用于记录非收集区指向收集区域的指针集合的抽象数据结构。
可供选择的记忆集:字长精度;对象精度;卡精度。
卡精度所指的是用一种称作卡表的方式来实现记忆集,也是目前最常用的实现记忆集的方式。卡表最简单的形式可以只是一个字节数组,HotSpot就是这样做的。字节数组的每一个元素都对应着其标识的内存区域中一块特定大小的内存块,被称作为卡页。如果卡页内有一个(或多个)对象的字段存在跨代指针,则对应的卡表的数组的值标识为1,称为这个元素变脏,没有标识为0.垃圾收集时,只筛选卡表中变脏的元素,就能得出哪些卡页内存块中包含跨代指针,把他们加入GC Root中一并扫描。
5.写屏障
维护卡表状态。
6.并发的可达性分析
对象消失问题,当且仅当以下两个条件同时满足:
- 赋值器插入了一条或多条从黑色对象到白色对象的新引用
- 赋值器删除了全部从灰色对象到白色对象的直接或间接引用
(注: 白色 表示对象尚未被垃圾回收器访问过;黑色 表示对象已经被垃圾回收器访问过,并且这个对象的所有引用都已经扫描过;灰色 表示对象已经别垃圾回收期访问过,但是这个对象至少存在一个引用还没有被扫描过)
解决:
- 增量更新:破坏第一个条件
- 原始快照:破坏第二个条件
两种解决方式都是通过写屏障实现
参考:周志明 深入理解Java虚拟机