聊一下G1垃圾回收器

1. 内存结构改变

G1的整个堆会被划分成多个大小相等的区域,称之为区 Region,区域不要求是连续的。分为Eden、SurvivorOld区。Region的大小通过堆空间大小/2048计算得到,也可以通过参数-XX:G1HeapRegionSize=32m指定(其中32m指定region大小为32M),Regionsize必须是2的指数幂,取值范围从1M到32M。

2. 垃圾回收过程

年轻代回收

  1. 新创建的对象会存放在Eden区。当G1判断年轻代区不足(max默认60% ),无法分配对象时需要回收时会执行 Young GC。
  2. 标记出Eden和Survivor区域中的存活对象
  3. 根据配置的最大暂停时间选择某些区域将存活对象复制到一个新的Survivor区中(年龄+1),清空这些区域。
  4. 后续Young GC时与之前相同,只不过Survivor区中存活对象会被搬运到另一个Survivor区
  5. 当某个存活对象的年龄到达阈值(默认15),将被放入老年代。
  6. 部分对象如果大小超过Region的一半,会直接放入老年代,这类老年代被称为Humongous区。比如堆内存是4G,每个Region是2M ,只要一个大对象超过了1M就被放入Humongous区,如果对象过大会横跨多个Region。
  7. 多次回收之后,会出现很多Old老年代区,此时总堆占有率达到阈值时(XX:InitiatingHeapOccupancyPercent默认45% )会触发混合回收MixedGc。回收所有年轻代和部分老年代的对象以及大对象区。采用复制算法来完成。

混合回收

混合回收分为:初始标记(initial mark)并发标记(concurrentmark)最终标记(remark或者FinalizeMarking)并发清理(cleanup) G1对老年代的清理会选择存活度最低的区域来进行回收,这样可以保证回收效率最高,这也是G1(Garbagefirst)名称的由来。

G1怎么选择哪些region优先回收?

G1在进行YoungGC的过程中会去记录每次垃圾回收时每个Eden区和Survivor区的平均耗时,以作为下次回收时的参考依据。这样就可以根据配置的最大暂停时间计算出本次回收时最多能回收多少个Region区域了。比如 -XX:MaxGCPauseMilis=n(默认200),每个Region回收耗时40ms,那么这次回收最多只能回收4个Region

触发 full gc

注意:如果清理过程中发现没有足够的空Region存放转移的对象,会出现FullGC。单线程执行标记-整理算法此时会导致用户线程的暂停。所以尽量保证应该用的堆内存有一定多余的空间。

G1垃圾回收器相关参数

参数1:-XX:+UseG1GC 打开G1的开关JDK9之后默认不需要打开

参数2:-XX:MaxGCPauseMilis=毫秒值最大暂停的时间

3. 卡表技术

引言:

如果做年轻代垃圾回收的时候,我们只关注年轻代,可能看不出什么问题。

但是放大视角,我们就会发现OLD Region可能会引用EDEN的对象,这些对象是不能被回收的

怎么解决这个问题呢?难道要搞个Set?老年代哪些对象引用了自己?

稀疏索引-CardTable

我们把一个Region以512B进行划分。

假如1M的Region,那么划分他机会划分为 2K个区域。

ok,你可能会问怎么计算一个对象对应的card索引?

计算一个对象对应的card索引

cartIndex = (对象起始地址 - 堆空间起始地址) / 512B

Remembered Set

每个Region独有的,他是一个类似HashMap。他代表的意义是哪些region指向了该region的对象。

Key是region地址,value是卡表索引。

怎么更新 Remembered Set?

假设现在 Region C(Old Region)里面的p对象要指向Region A(EDEN Region)里面的e对象。

就会执行下列代码

java 复制代码
p.e = e; 

Write Barrier

  • JVM注入的一小段代码,用于记录指针变化

object.filed = < reference >

  • 当执行上述代码的时候(相当于AOP操作)

标记object对应的Card为Dirty

将Card存入 Dirty Card Queue

并不是立即更新Rset,而是把这个脏Card放入队列里面(相当于消息队列)

什么时候把 Dirty Card Queue 里面的脏Card刷新到 Rset?

Dirty Card Queue是四种状态,

当脏Card容量还没超过 白色区域 ,无事发生。

当脏Card容量已经到达 绿色区域 ,会有一个 Refinement 线程开始被激活,开始更新 Rset

当脏Card容量已经到达 黄色区域 ,全部 Refinement 线程开始激活,开始更新 Rset

当脏Card容量已经达到 红色区域应用线程 也开始参与排空队列的工作 , 开始更新 Rset

总结

如果我们要回收某个region的时候,就可以去检查他的RSet,就能获得其引用了自身的Card,然后只需要遍历这些Card即可。就不需要再去遍历整个堆空间去检查哪些对象不能回收。采用了Write Barrier技术 + 空间换时间思想。

加入 cardTable 后的年轻代回收过程

  1. Root扫描,将所有的静态变量、局部变量扫描出来。
  2. 处理脏卡队列中的没有处理完的信息,更新记忆集的数据,此阶段完成后,记忆集中包含了所有老年代对当前 Region的引用关系。
  1. 标记存活对象。记忆集中的对象会加入到GCRoot对象集合中,在GCRoot引用链上的对象也会被标记为存活对象。
  2. 根据设定的最大停顿时间,选择本次收集的区域,称之为回收集合Collection Set。
  3. 复制对象: 将标记出来的对象复制到新的区中,将年龄加1,接清空如果年龄到达15,则晋升到老年代。老的区域内存直接清空
  4. 处理软、弱、虚、终结器引用,以及JNI中的弱引用。

4. SATB技术

前言

多次回收之后,会出现很多Old老年代区,此时总堆占有率达到闽值(默认45% )时会触发混合回收MixedGC。 混合回收会由年轻代回收之后或者大对象分配之后触发 ,混合回收会回收 整个年轻代 + 部分老年代。老年代很多时候会有大量对象,要标记出所有存活对象耗时较长,所以整个标记过程要尽量能做到和用户线程并行执行。

混合回收的步骤

  1. 初始标记,STW,采用三色标记法标记从GC Root可直达的对象。
  2. 并发标记,并发执行,对存活对象进行标记。
  3. 最终标记,STW,处理SATB相关的对象标记
  4. 清理,STW,如果区域中没有任何存活对象就直接清理。
  5. 转移,将存活对象复制到别的区域。

三色标记计算法

初始标记会暂停所有用户线程,只标记从GCRoot可直达的对象,所以停顿时间不会太长。采用三色标记法进行标记三色标记法在原有双色标记(黑也就是1代表存活,白0代表可回收)增加了一种灰色,采用队列的方式保存标记为灰色的对象。

三色标记中的黑色和白色是使用位图(bitmap)来实现的,比如8个字节使用1个bit来标识标记的内容,黑色为1白色为0灰色不会体现在位图中,会单独放入一个队列中。如果对象超过8个字节,仅仅使用第一个bit位处理。

对象全部标记完,就剩下黑色和白色。(黑色的保留,白色的回收)

并发标记出现的问题

三色标记存在一个比较严重的问题,由于用户线程可能同时在修改对象的引用关系,就会出现错标的情况,比如这个案例中正常情况下,B和C都会被标记成黑色。但是在BC标记前,用户线程执行了B.=null;将B到c的引用去除了。

同时执行了A.c=c; 添加了A到c的引用。此时会出现错标的情况,C是白色可回收。

SATB

G1为了解决这个问题,使用了SATB技术(Snapshot At The Beginning,初始快照)。SATB技术是这样处理的:

  1. 标记开始时创建一个快照,记录当前所有对象,标记过程中新生成的对象直接标记为黑色。
  2. 采用前置写屏障技术,在引用赋值前比如B.c= null之前,将之前引用的对象c放入SATB待处理队列中。SATB队列每个线程都有一个,最终会汇总到一个大的SATB队列中。

最终标记 会暂停所有用户线程,主要是为了处理SATB相关的对象标记。这一步中,将所有线程的SATB队列中剩余的类据合并到总的SATB队列中,然后逐一处理。SATB队列中的对象,默认按照存活处理,同时要处理他们引用的对象。SATB的缺点是在本轮清理时可能会将不存活的对象标记成存活对象,产生了一些所谓的浮动垃圾,等到下一轮清理时才能回收。

相关推荐
Vitalia37 分钟前
从零开始学 Rust:基本概念——变量、数据类型、函数、控制流
开发语言·后端·rust
猎人everest4 小时前
SpringBoot应用开发入门
java·spring boot·后端
孤雪心殇9 小时前
简单易懂,解析Go语言中的Map
开发语言·数据结构·后端·golang·go
小突突突10 小时前
模拟实现Java中的计时器
java·开发语言·后端·java-ee
web1376560764311 小时前
Scala的宝藏库:探索常用的第三方库及其应用
开发语言·后端·scala
闲猫11 小时前
go 反射 interface{} 判断类型 获取值 设置值 指针才可以设置值
开发语言·后端·golang·反射
LUCIAZZZ12 小时前
EasyExcel快速入门
java·数据库·后端·mysql·spring·spring cloud·easyexcel
Asthenia041212 小时前
依托IOC容器提供的Bean生命周期,我们能在Bean中做些什么?又能测些什么?
后端
Ase5gqe12 小时前
Spring中的IOC详解
java·后端·spring
小万编程12 小时前
基于SpringBoot+Vue奖学金评比系统(高质量源码,可定制,提供文档,免费部署到本地)
java·spring boot·后端·毕业设计·计算机毕业设计·项目源码