JVM的跨代引用问题是指在Java堆内存的不同代之间存在引用关系,导致对象在不同代之间的引用被称为跨代引用。比如:新生代到老年代的引用,老年代到新生代的引用等。
跨代引用会有什么问题呢?
关于这个知识点,我看了网上很多资料,基本上没有哪个能解释的很清楚的,包括《深入理解Java虚拟机(第三版)》也是只说了其中的一部分,我综合了很多资料,加上自己的理解,试图把这个事讲的清楚一点。
首先看下面这张图:
假如,我们现在JVM的堆是上面这种情况,那么在进行一次MinorGC(YoungGC)的时候,会从GC Root出发,然后进行可达性分析,假如当前正在进行一次Young GC,如果他发现一个对象处于老年代,那么JVM就会中断这条路径。
那么这时候,JVM就会认为只有A和B是可达的,就会在接下来的Young GC中把E回收掉,但是其实E是有引用的,只不过他的引用在老年代,发生了跨代引用。
想要解决解决这个问题,有两种简单的做法:
1、在做YoungGC的时候,GC Root出发后扫描到老年代对象后不中断,继续扫描和标记,把所有在年轻代的对象都标记上。
2、在YoungGC的实时,把老年代的所有对象也作为GC Root,进行可达性分析扫描。
以上两种做法,其实成本都太高了,甚至第一种要比第二种成本还要高,因为他不仅要扫描,还需要不断地做标记。
那么,于是就有一个好的办法出现了,那就是定义了一个全局的数据结构——Remembered Set。
Remembered Set 的主要作用是跟踪老年代对象与年轻代)对象之间的引用关系,以帮助识别老年代中存活对象。他的核心目标是减少全堆扫描的开销。老年代中的对象通常存活更长时间,因此为了回收年轻代,JVM需要知道哪些老年代对象引用了年轻代对象,以确保不会错误地回收正在被老年代引用的年轻代对象。
Remembered Set 记录了老年代对象指向年轻代对象的引用关系,此后当发生Minor GC时,垃圾回收器不需要扫描整个老年代来确定哪些对象存活。它只需扫描Remembered Set中的条目,从而减少了扫描的开销。
所以,在Remember Set中的对象也会被加入到GC Roots进行扫描:
而在Remembered Set的实现中,比较常见的一种叫做Card Table。
以上,就是关于跨代引用的介绍,关于Card Table的具体实现,以及在CMS和G1中解决跨代引用的具体实现,我后面单独讲解。这里先占个坑。