【陪你学JVM】G1垃圾回收器

概要

G1垃圾回收器从JDK9开始被认定为默认的垃圾回收器。学习最好的方法就是看官方文档,官方文档如下:
G1官方文档链接
G1源码链接
源码下载指导

核心特色

作为被官方默认的垃圾回收器。G1的主要特点体现在以下几个方面:

1、内存模型采用分区Region

不同于以往的垃圾回收器(serial,parallel,CMS)将堆划分为固定的年轻代,老年代和永久代。
Hotspot堆结构
G1将堆划分成一个个大小相等的区域(region),每个区域都可能是 【eden, survivor, old】的任意一种,所以这三种【eden, survivor, old】都不是固定大小,因为每个的数量都可能是任意的。
G1内存划分

2、SATB(Snapshot at the Beginning)初始快照

GC开始时,先创建一个对象快照,快照中所有当前活的对象在并发标记时,都认识是活的,并且并发标记过程中所有新建的对戏那个都认为是活的。
其中又有两个关键的位图(prevBitmap和nextBitmap)
源码位置:hotspot/share/gc/g1/g1ConcurrentMark.hpp

G1CMBitMap* _prev_mark_bitmap; // 记录上一轮标记结果的位图 Completed mark bitmap
G1CMBitMap* _next_mark_bitmap; // 记录当前轮次标记的位图 Under-construction mark bitmap

prevBitmap 存储最近一次完整并发标记周期的存活对象信息;
nextBitmap 在并发标记过程中动态更新,最终会覆盖 prevBitmap

位图与Region的关联
_prev_top_at_mark_start(prevTAMS)
_next_top_at_mark_start(nextTAMS)

​​prevTAMS​​:指向上一轮标记结束时 Region 的分配位置;
​​nextTAMS​​:指向当前轮次标记开始时的分配位置,用于区分隐式存活对
G1内存划分
A阶段,初始标记阶段,通过STW,将region的top赋值给nextTAMS;

A-B阶段,并发标记阶段;

B阶段,是并发标记结束阶段。并发标记阶段生成的新对象都会放在[nextTAMS,top]之间,这边对象被定义为“隐式对象”,同时nextBitmap也记录了bottom到nextTAMS之间标记对象的地址;

C阶段,是垃圾清理阶段。会交换prevBitmap和nextBitmap,同时清理[bottom, preTAMS]之间标记后的垃圾,对应的“隐式对象”,在下个阶段才会被标记清理。

初始快照解决的问题:被引用的对象错误的被垃圾回收问题。
问题详细说明:当在并发标记阶段(concurrent mark)使用三色标记算法对对象进行标记时。由于并发标记及CPU调度,所以可能会造成下面情形。

  • 时刻1:某个白对象D被灰色对象B引用,那么灰色对象B一定被黑色对象D引用。此时GC线程恰好被终止。
D
B
A
  • 时刻2:GC线程重新运行,灰色对象B不再引用白色对象,并且黑色对象D引用了A对象。
D
A
B

此时再扫描就会将A对象作为垃圾被回收。这种错误回收会在D调用A对象的时候报错,为了解决这个问题,G1使用SATB来解决。

SATB机制通过写屏障(write barrier)和并发标记队列解决漏标问题。
源码:
1、写屏障记录旧引用
hotspot/share/gc/g1/g1BarrierSet.cpp

G1BarrierSet::write_ref_array_pre_work(T* dst, size_t count) {
  if (!_satb_mark_queue_set.is_active()) return; //检查SATB队列是否启用
  T* elem_ptr = dst; //获取数组元素指针
  for (size_t i = 0; i < count; i++, elem_ptr++) { 
    T heap_oop = RawAccess<>::oop_load(elem_ptr);  //加在旧引用值
    if (!CompressedOops::is_null(heap_oop)) {
      enqueue(CompressedOops::decode_not_null(heap_oop));  //将旧引用加入到队列中。
    }
  }
}

那么他是怎么来实现这一过程的呢?
就是依赖两种结构 Card Table 和 Remember Set(RSet)。
Card Table :在每个region中,每512个字节会被分为一块称之为一个Card,每当这个Card中的发生改变时,那么该Card就会被标记为dirty。

Remember Set:每个region都会维护一个Remember Set记录的就是哪些Card指向了当前region,也就是所谓的dirty Card会被记录在RSet中。

而记录的过程就是通过写屏障(write barrier)来实现的,也就是当引用发生变化时,JVM会注入一段代码,将这个变化保存到RSet中。

但由于RSet是一个region共享的,所以在写RSet的时候,势必会造成竞争写的场景,并且多个线程频繁的切换上下文也同样会导致性能问题。
所以,G1的做法并不是直接去写RSet,而是每个线程去维护一个Dirty Card Queue,只有当满足一定情况,即队列的长度达到一定长度时,才会去讲这些Dirty Card对应的引用写入到RSet中。
为了更有效率的处理Dirty Card Queue,G1把这个队列分成了四个区域,从队首到队尾分别为白、绿、黄、红四个区域。当队列中的数据达到不同的区域会采取不同的措施去处理。开发者也可以通过配置参数来设置每一个区域的大小,参数为:-XX:G1ConcRefinementGreen/Yellow/RedZone=来做配置。
当为white白区时,不会做任何处理。
当为green绿区时,Refinement线程被激活,开始更新RSet。
当为yellow黄区时,全部的Refinement线程被激活。
当为Red红区时,应用线程也会参与到处理队列当中的数据的任务中。

参考列表
1、博客园:修心而结网:G1的SATB
2、腾讯元宝AI工具
3、哔哩哔哩:饥人谷后端开发:新一代垃圾回收器:G1详解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值