为什么跨代引用是GC root

作者: flycash | 来源:发表于2017-11-04 15:13 被阅读57次

昨日,有人在一个JVM群里问了一个问题,为什么跨代引用是gc root。这虽然是一个很简单的问题,但是其实涉及到了分代垃圾回收算法的核心理念。

gc root的基本解释

首先我们要理解一下GC root究竟是什么东西。

gc root

堆是被我们垃圾回收所管理的内存空间。如图,存在两种引用,一种是堆外对象对堆内对象的引用,被标注为红色;另外一种是堆内对象之间的引用,被标注为灰色。通常我们说的gc root就可以被认为是红色的那种引用,比如说栈引用堆中对象。为什么我们不认为堆内对象之间的引用是gc root呢?因为我们的对象,最终是要被外部使用的,比如说被栈引用所访问。因此,如果一大堆的堆内对象之间互相引用,但是没有任何堆外部引用,那么这部分对象实际上也是不可达的。HotSpot就是如此的,所有的堆中的对象,最终都是被栈所使用的。因而,U和V就可以看做是不可达的对象了。

分代和跨代引用

解释了gc root的基本概念后,我们要来看看分代理论了。基本上,现代垃圾回收器都是分代垃圾回收器,它建立在两个分代理论之上:

  • 弱分代假说(weak generational hypothesis):大多数对象在年轻的时候死亡;
  • 强分代假说(strong generational hypothesis):越老的对象越难死亡;

这个分代假说引申出一种垃圾回收理念:将对象依据“年龄”分配到不同的区域,每次回收只回收其中的一个区域。这也就是分代回收的基础理念。因为很显然的,如果大部分对象都是朝生夕死的,那么将它们放在一起,每次回收都能够回收到很多的空间;剩下的不容易死亡的对象,放在一起,那么可以以一种极为低的频率来回收它们。这就兼顾了垃圾回收的时间开销和内存的空间利用率。

一般的垃圾回收算法至少会划分出两个年代,年轻代和老年代。但是单纯的分代理论在垃圾回收的时候存在一个巨大的缺陷:为了找到年轻代中的存活对象,却不得不遍历整个老年代,反过来也是一样的。

跨代引用引起老年代的遍历

如果我们从年轻代开始遍历,那么可以断定N, S, P, Q都是存活对象。但是,V却不会被认为是存活对象,其占据的内存会被回收了。这就是一个惊天的大漏洞!因为U本身是老年代对象,而且有外部引用指向它,也就是说U是存活对象,而U指向了V,也就是说V也应该是存活对象才是!而这都是因为我们只遍历年轻代对象!

所以,为了解决这种跨代引用的问题,最笨的办法就是遍历老年代的对象,找出这些跨代引用来。这种方案存在极大的性能浪费。因为从两个分代假说里面,其实隐含了一个推论:跨代引用是极少的。也就是为了找出那么一点点跨代引用,我们却得遍历整个老年代!从上图来说,很显然的是,我们根本不必遍历R。

因此,为了避免这种遍历老年代的性能开销,通常的分代垃圾回收器会引入一种称为记忆集的技术。简单来说,记忆集就是用来记录跨代引用的表。

记忆集记录跨代引用

如图,在拥有记忆集的情况下,我们就可以不用遍历老年代了,这是一个巨大的性能提升!

最终解释

现在,我们设想一下,要回收年轻代,首先我们要从引用年轻代对象的外部引用开始;其次,我们要从跨代引用开始。于是我们可以很自然的得出结果:跨代引用也是gc root。

整个模型可以抽象成:


gc root的最终解释

附录

在引入记忆集之后,其实会有一个很有意思的问题:即老年代对象即便已经事实上不可达了,但是因为记忆集的存在,会导致从该对象出发的跨代引用依旧会被当成gc root,直至该对象被回收引起记忆集中相关条目的擦除。

记忆集引出的问题

如图,U已经不存在外部引用了,所以它事实上已经不可达了。但是在这个时刻,因为老年代没有发生GC,所以它依旧存活着。

  • 如果我们采用遍历老年代的方法找出跨代引用,那么我们只能找到S->P这一条。于是U和V都会被当成是不可达对象,其内存空间就可以被回收掉了。
  • 如果我们使用记忆集,那么因为U没有被GC掉,所以记忆集里面的条目U->V依旧存在,所以在年轻代回收的时候,V会被当成存活对象。

这个问题就是因为使用记忆集带来的“滞后性”,它提高了时间效率,但是却降低了空间利用率。不过无论如何,它依然确保了垃圾回收所遵循的原则:垃圾回收确保回收的对象必然是不可达对象,但是不确保所有的不可达对象都会被回收

相关文章

  • 为什么跨代引用是GC root

    昨日,有人在一个JVM群里问了一个问题,为什么跨代引用是gc root。这虽然是一个很简单的问题,但是其实涉及到了...

  • GCRoots

    转载Java中什么样的对象才能作为gc root,gc roots有哪些呢? java的gc为什么要分代? 所谓"...

  • Java常见的GC Root

    我们知道Java 进行GC的时候会从GC root进行可达性判断,常见的GC Root有如下: 通过System ...

  • GC Root:

    www.jianshu.com/p/5db05db4f5ab www.jianshu.com/p/6ab2a0f7...

  • gc root

    一、oopMap oop (ordinary object pointer) 普通对象指针,oopmap就是存放这...

  • jvm 优化篇-(8)-跨代引用问题(RememberSet、C

    1、什么是跨代引用? 红色的线表示由虚拟机栈中发出的引用。显然B--->A、E--->F都是跨代引用。 2、跨代引...

  • GC - 记忆集、卡表及写屏障

    记忆集与卡表 跨代引用 跨代引用是指新生代中存在对老年代对象的引用,或者老年代中存在对新生代的引用。 新生代引用老...

  • JVM GC

    分代GC JVM的GC机制叫做分代GC(generational GC),把内存分为两种:新生代(Young Ge...

  • 什么是GC ROOT?有哪些GC ROOT?

    上⾯提到的标记的算法,怎么标记⼀个对象是否存活?简单的通过引⽤计数法,给对象设置⼀个引⽤计数器,每当有⼀个地⽅引⽤...

  • 二、GC ROOT

    jvm在判断对象是否还有用的时候,通常会从一些常见的地方开始进行搜索,只要对象不能从这些地方被引用到,就判断这些没...

网友评论

    本文标题:为什么跨代引用是GC root

    本文链接:https://www.haomeiwen.com/subject/dvgfmxtx.html