jvm垃圾收集之跨代引用
跨代引用
目前的垃圾收集器多数都是基于分代收集理论进行的。以Parnew+cms垃圾收集器为例,假设要进行一次新生代的垃圾收集,但新生代的对象是完全有可能被老年代中的对象引用的,为了判断新生代中某个对象是否存活,需要额外遍历整个老年代来确保可达性分析的正确性,反过来也是一样。这种方案固然可行,但同时给垃圾收集带来很大的性能负担。
注:不仅新生代、老年代存在跨代引用,G1的rigen之间也存在跨代引用,即所有涉及到部分区域收集的收集器都面临这样的问题。
记忆集和卡表
为了解决这个问题,jvm引入了记忆集和卡表,用于避免将非收集区域加到GcRoot的扫描范围。
记忆集是一种数据结构,用于记录从非收集区域指向收集区域的指针集合(摘抄于虚拟机这本书)。
解释下,假设需要对新生代进行垃圾收集,需要在新生代中维护一个数据结构,用于标记哪些老年代的内存有对新生代的引用。GCRoot扫描时,只需对含有对新生代引用的老年代部分进行扫描即可。
卡表又是什么呢?
卡表是对记忆集的一种实现(类似于 HashMap 和Map的关系)。即,记忆集是一种思想,可以通过很多实现方式实现,卡表就是其中一种实现,也是目前最常用的一种实现方式。
hotSpot虚拟机中卡表的实现方式是一个数组。
CARD_TABLE[this.address>>9]=0;
OR
CARD_TABLE[this.address>>9]=1;
数组CARD_TABLE每一个元素都对应一块固定大小的内存块,称为内存页,一个内存页的大小是2的9次幂(512字节),相当于内存位置除与512。即将连续的内存划分为大小为512字节的若干块,CARD_TABLE[0]对应第一块,CARD_TABLE[1]对应第二块,CARD_TABLE[1000]对应第1001块内存。
很明显,一个内存页可以存放很多对象,只要其中有一个对象存在跨代引用,那个数组对应的值就为1,称之为脏页。在进行垃圾收集时,只要扫表脏页部位的内存即可。