• 根节点枚举
○ 为保证一致性,所有收集器在根节点枚举这一步骤时都是必须暂停用户线程的。
○ 现在可达性分析算法耗时最长的查找引用链的过程已经可以做到与用户线程一起并发,但根节点枚举始终还是必须在一个能保障一致性的快照中才得以进行。
○ 虚拟机直接得到哪些地方存在着对象:使用一组称为OopMap的数据结构,一旦类加载动作完成的时候,HotSpot就会把 对象内 什么偏移量上 是什么类型的数据计算出来,在即时编译过程中,也会在特定的位置记录下栈里和寄存器里哪些位置是引用。这样收集器在扫描时就可以直接得知这些信息了,并不需要真正一个不漏地从方法区等GCRoots开始查找。
• 安全点(safe point):HotSpot不会为每条指令都生成OopMap,只在“特定的位置”记录这些信息,这些位置被称为安全点(Safepoint)。
○ 安全点的位置(方法调用、循环跳转、异常跳转)
§ 循环的末尾
§ 方法临返回前 / 调用方法的call指令后
§ 可能抛异常的位置
○ 抢先式中断:系统定期中断/恢复用户线程,直到其运行到安全点时被中断。(基本不用)
○ 主动式中断:由线程主动中断,需要代码配合。在安全点处设置一个标志位,线程执行过程中不断轮询,一旦为真即中断线程。
§ 轮询指令:使用频繁,要求效率较高,仅有一条汇编指令。
• 安全区域(Safe Region):主动式中断要求线程在运行过程中不断轮询标志位,以达到进入垃圾回收状态的目的。但当线程处于sleep/blocked状态时,系统不可能等待线程苏醒,于是引入安全区域。
○ 安全区域是指能够确保在某一段代码片段之中,引用关系不会发生变化,因此,在这个区域中任意地方开始垃圾收集都是安全的。我们也可以把安全区域看作被扩展拉伸了的安全点。
• 记忆集(Remembered Set)
○ 为解决对象跨代引用所带来的问题,垃圾收集器在新生代中建立了名为记忆集的数据结构,用以避免把整个老年代加进GC Roots扫描范围。(参考2.2的跨代引用假说)
○ 跨代引用问题:老年代中存在对新生代的引用,由跨代引用假说,不可能在对新生代GC时扫描整个老年代。若此时针对新生代单独进行GC(如Minor GC),则老年代中引用指向的新生代对象有可能被回收,导致老年代中出现悬空指针,造成GC的结果不确定甚至程序崩溃。反之亦然。
§ 解决办法:将老年代中存在跨代引用的对象加入GC Roots
○ 记忆集的作用:记录哪些对象存在跨代引用,即记录哪些对象需要加入GC Roots
○ 记忆集的记录精度
§ 记忆集用于记录哪些对象存在跨代引用,但不可能将这些对象的指针都记录下来。
§ 在垃圾收集的场景中,收集器只需要通过记忆集判断出某一块非收集区域是否存在有指向了收集区域的指针就可以了,并不需要了解这些跨代指针的全部细节。
§ 例子
□ 字长精度
□ 对象精度
□ 卡精度:每个记录精确到一块内存区域,该区域内有对象含有跨代指针。
○ 卡表:目前最常用的一种记忆集实现形式。
§ 卡表最简单的形式可以只是一个字节数组,而HotSpot虚拟机确实也是这样做的。
§ 字节数组CARD_TABLE的每一个元素都对应着其标识的内存区域中一块特定大小的内存块,这个内存块被称作“卡页”(Card Page)。一般来说,卡页大小都是以2的N次幂的字节数。
§ 一个卡页的内存中通常包含不止一个对象,只要卡页内有一个(或更多)对象的字段存在着跨代指针,那就将对应卡表的数组元素的值标识为1,称为这个元素变脏(Dirty),没有则标识为0。
• 写屏障与AOP,并发的可达性分析与三色标记
○ 直接参考博客:三色标记法与读写屏障,写得很清楚
参考资料:《深入理解Java虚拟机 第三版》周志明