G1卡表详解

跨代引用面临的问题
首先,产生跨代引用场景是发生YongGC的过程。此时新生代的对象会开始寻找根,看自己是否属于根可达对象,从而判断自己是否是垃圾。

那很多同学就开始有疑惑了?不是判断对象是否存活,应该是从GC Roots开始寻找,使用复杂的三色标记算法后,将判定不存活的对象删除掉么?

但我们知道,并不是所有老年代的对象都会引用着新生代的对象。那么相对频繁的YongGC,每次都从根节点遍历一次,效率就会被严重影响。

因此,就引入了卡表和记忆集的概念。卡表将整个老年代分成了多个层级,card[0],card[1],card[2]…。如果某个card区域中的老年代对象引用着新生代的对象,那么就被叫做脏卡。当YongGC发生时,某个新生代的对象发现其GCRoots发现在老年代,并进行跨代寻找的时候,只需要在对这些脏卡中的GCRoots,使用可达性分析算法,判断是否存活即可。加速了垃圾回收的速度与回收成本。

那么具体是如何执行的呢?我们接着看。

GCRoots在新生代
在这里插入图片描述

假设现在整个堆空间只有两个对象。此时两个对象都在新生代。此时GC线程是非常容易判断这两个对象被GCRoots引用,属于存活对象。

随着程序的长时间运行。此时出现了以下情况:

GCRoots移动到老年代
在这里插入图片描述

可以看到,老年代的对象HumongN被GCRoots所引用,此时HumongN->S就是跨代引用。S又引用着E,因此这三个对象都不是垃圾。

我们说在新生代中,由E找到S是非常快速简单的。然而由S找到HumongN就需要遍历整个老年代的对象,这个过程是相当耗时的。所以要避免每次 YGC 时扫描整个老年代,减少开销。

解决跨代引用流程
在这里插入图片描述

在判断新生代对象是否根可达时,一部分对象是朝生夕死的对象,而另一部分可能是有由相当老年代的对象引用而来的。而一般老年代的引用关系都相当复杂,为了节约扫描时间成本,我们在每个新生代对象中存入一个RSet记录卡表。在检索新生代引用即将跨代时,会根据卡表的Key,Value快速定位到正确的老年代引用,以达到减少开销的目的。

RSet(记忆集)
记录了其它Region中的对象到Region的引用。

RSet的价值在于使得垃圾回收不需要扫描整个堆,能够快速定位到真正引用它的堆对象地址。ReSet本身就是一个Hash表,存储在新生代的每个Region中。但是存储需要消耗空间,多的能达到百分之20。因此G1对内存的空间要求较高(小空间没资本玩),空间越大性能越彪悍。

CardTable
由于新生代GC时,需要扫描整个old区,效率非常低。所以old区就是用卡表的方式进行一次逻辑分区。一般一页卡表的大小是2的n次幂。每一个区域也是用Key,Value结构进行记录。每一区域记录为Key不重复,Value则记录这片区域的老年代对象与新生代对象是否存在引用关系,存在则标记为1,否则为0。记录完毕后把value为1的key作为ReSet的key进行记录,并且ReSet的value存储引用,从而提高跨代引用的查询效率。

总结
G1的设计因为这些记录,划分Region的原因,对基础内存的要求比较高。CMS也存在ReSet与CardTabe。但是因为CMS没有Region的存在,新生代只有一份,所以只需要记录一份,对空间要求不高。所以虽然G1优点很多,但是不推荐堆空间小于6G以下使用G1。

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值