安全区域
使用安全点的设计似乎已经完美的解决如何停顿用户线程,让虚拟机进入垃圾回收状态的问题了,但实际情况却并不一定。安全点机制保证了程序执行时,在不太长的时间内就会遇到可进入垃圾收集过程的安全点。但是程序“不执行”的时候呢?所谓的程序不执行就是没有分配处理器时间,典型场景就是用户线程处于Sleep状态或者Blocked状态,这时候用户线程就无法响应虚拟机的中断请求,不能再走到安全的地方去中断挂起自己,虚拟机也显然不可能持续等待线程重新被激活分配处理器时间。对于这种情况,就必须引入安全区域(Safe Region)来解决。
安全区域:指能够确保在某一段代码片段之中,引用关系不会发生变化,因此,在这个区域中任意地方开始垃圾收集都是安全的。
当用户线程执行到安全区域里面的代码时,首先会标识自己已经进入安全区域 ,那样当这段时间里虚拟机要发起垃圾收集时就不必去管这些已声明自己在安全区域内的线程了。当线程要离开安全区域时,它要检查虚拟机是否已经完成了根节点枚举(或者垃圾收集过程中其他需要暂停用户线程的阶段),如果完成了,那线程就当作没事发生过,继续执行;否则它就必须一直等待,直到收到可以离开安全区域信号为止。
记忆集与卡表
记忆集是一种记录从非收集区域指向收集区域的指针集合的抽象数据结构。
在垃圾收集的场景中,收集器只需要通过记忆集判断出某一块非收集区域是否存在有指向了收集区域的指针就可以了,并不需要了解这些跨代指针的全部细节。那设计者在实现记忆集的时候,便可以选择更为粗犷的记录精度来节省记忆集的存储和维护成本,以下是一些可供选择的记录精度:
- 字长精度:每个记录精确到一个机器字长(就是处理器的寻址位数,如常见的32位或64位,这个精度决定了机器访问物理内存地址的指针长度),该字包含跨代指针。
- 对象精度:每个记录精确到一个对象,该对象里有字段包含跨代指针。
- 卡精度:每个记录 精确到一块内存区域,该区域内有对象包含跨代指针。
”卡精度“所指的是用一种称为”卡表“(Card Table)的方式去实现记忆集。卡表是记忆集的一种具体实现,它定义了记忆集的记录精度、与堆内存的映射关系等。
卡表最简单的形式可以只是一个字节数组,而HotSpot虚拟机确实也是这样做的。以下这行代码是HotShot默认的卡表标记逻辑:
CARD_TABLE [this address >>9] = 0;
字节数组CARD_TABLE的每一个元素都对应着其标识的内存区域中一块特定大小的内存块,这个内存块被称为”卡页“。一般来说卡页大小都是以2的N次幂的字节数。(上面代码使用的卡页是2的9次方幂),如果CARD_TABLE的第0、1、2号元素分别对应地址范围为0x000-0x01FF、0x0200-0x03FF、0x0400-0x05FF的卡页内存块。如下图:
一个卡页的内存中通常包含不止一个对象,只要卡页内有一个(或更多)对象的字段存在着跨代指针,那就将对应卡表的数组元素值标识为1,称这个元素变脏(Dirty),没有则0.在垃圾收集发生时,只要帅选出卡表中变脏的元素,就能轻易得出哪些卡页内存中包含跨代指针,把它们加入GC Roots中一并扫描。