G1收集器 - 垃圾回收器(GC)

介绍

定义

JDK 9以后默认使用,替代了CMS 收集器

G1 收集器的最大特点(相比较CMS

  • G1 最大的特点是引入分区的思路,弱化了分代的概念。
  • 并行与并发:G1 能充分利用 CPU、多核环境下的硬件优势,使用多个 CPU(CPU 或者 CPU 核心)来缩短 Stop-The-World 停顿时间。部分其他收集器原本需要停顿 Java 线程执行的 GC 动作,G1 收集器仍然可以通过并发的方式让 java 程序继续执行。
  • 空间整合:与 CMS 的“标记-清除”算法不同,G1 从整体来看是基于“标记-整理”算法实现的收集器,不会产生空间碎片;从局部上来看是基于“标记-复制”算法实现的。
  • 可预测的停顿:G1垃圾回收器设定了用户可控的停顿时间目标,开发者可以通过设置参数来指定允许的最大垃圾回收停顿时间。G1会根据这个目标来动态调整回收策略,尽可能地减少长时间的垃圾回收停顿。

如何完成可预测的?
G1根据历史数据来预测本次回收需要的堆分区数量,也就是选择回收哪些内存空间。最简单的方法就是使用算术的平均值建立一个线性关系来进行预测。比如:过去10次一共收集了10GB的内存,花费了1s。那么在200ms的时间下,最多可以收集2GB的内存空间。而G1的预测逻辑是基于衰减平均值和衰减标准差来确定的。

G1一定不会产生内存碎片吗

堆内存的动态变化、分配模式以及回收行为等因素影响下,仍然可能出现一些碎片问题。当某些Region中存在多个不连续的小块空闲内存,无法完全满足某些大对象的内存需求时,仍然可以称之为碎片问题。

  1. 分配模式不规律: 如果应用程序的内存分配模式不规律,频繁地分配和释放不同大小的对象,可能会导致一些小的空闲内存碎片在堆中产生。
  2. 大对象分配: G1回收器的区域被划分为不同大小的Region,当一个大对象无法在单个Region中分配时,G1可能会在多个Region中分配这个大对象,这可能导致跨多个Region的碎片。
  3. 并发情况下的内存变化: G1回收器会在后台进行并发的垃圾回收,如果在回收过程中发生了内存变化,如某个区域中的对象被回收,留下一些零散的空闲空间,也有可能会导致内存碎片。
  4. 频繁的Full GC: 尽管G1垃圾回收器的设计可以减少Full GC(全局垃圾回收)的频率,但如果频繁发生Full GC,可能会导致内存布局的重组,产生一些碎片。

CMS 和 G1 的区别

  • CMS 中,堆被分为 PermGen,YoungGen,OldGen ;而 YoungGen 又分了两个 survivo 区域。在 G1 中,堆被平均分成几个区域 (region) ,在每个区域中,虽然也保留了新老代的概念,但是收集器是以整个区域为单位收集的。
  • G1 在回收内存后,会立即同时做合并空闲内存的工作;而 CMS ,则默认是在 STW(stop the world)的时候做。
  • G1 会在 Young GC 中使用;而 CMS 只能在 Old 区使用

内存模型调整:

G1 堆内存结构

  • 堆内存会被切分成为很多个固定大小区域(Region),每个是连续范围的虚拟内存。
  • 堆内存中一个区域 (Region) 的大小,可以通过 -XX:G1HeapRegionSize 参数指定,大小区间最小 1M 、最大 32M ,总之是 2 的幂次方。
  • 默认是将堆内存按照 2048 份均分。

G1堆内存分配

  • 每个 Region 被标记了 E、S、O 和 H,这些区域在逻辑上被映射为 Eden,Survivor 和老年代。存活的对象从一个区域转移(即复制或移动)到另一个区域。区域被设计为并行收集垃圾,可能会暂停所有应用线程。如上图所示,区域可以分配到 Eden,survivor 和老年代。
  • 此外,还有第四种类型,被称为巨型区域(Humongous Region)。Humongous 区域主要是为存储超过 50% 标准 region 大小的对象设计,它用来专门存放巨型对象。如果一个 H 区装不下一个巨型对象,那么 G1 会寻找连续的 H 分区来存储。为了能找到连续的 H 区,有时候不得不启动 Full GC

G1对大对象的处理

  1. 大对象的分配:
    G1垃圾回收器将堆内存多划分出一个区域,被称为巨型区域(Humongous Region)。Humongous 区域主要是为存储超过 50% 标准 region 大小的对象设计,它用来专门存放巨型对象。
  2. 大对象的回收:
    G1回收器会识别并处理不再使用的大对象。当一个大对象成为垃圾时,它会被添加到回收集(Collection Set)中,并在进行垃圾回收时进行回收。G1回收器会在Young区和Old区之间进行垃圾回收,并根据其适应性算法选择优先处理哪些区域。对于大对象,G1会根据空间回收的需要进行回收。
  3. Full GC:
    G1回收器在大多数情况下不会发生Full GC(全局垃圾回收)。与传统的垃圾回收器不同,G1的设计目标之一是减少长时间的垃圾回收停顿。它通过将堆内存划分为多个Region,并在后台进行部分回收,避免了传统垃圾回收器中会出现的整个堆内存的停顿。如果一个 H 区装不下一个巨型对象,那么 G1 会寻找连续的 H 分区来存储。为了能找到连续的 H 区,仍然可能触发Full GC。

G1 回收流程

在执行垃圾收集时,G1 以类似于 CMS 收集器的方式运行。

G1(Garbage-First)垃圾回收器的年轻代(Young Generation)垃圾回收(Young GC)是一种增量式的回收过程,它采用了“混合回收”(Mixed Garbage Collection)的方式。这意味着G1在执行年轻代的垃圾回收时,除了处理年轻代的对象,还会同时处理一部分老年代的对象

G1 收集器的阶段,大致分为以下步骤:
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. G1 执行的第一阶段:初始标记 (Initial Marking)
    这个阶段是 STW (Stop the World) 的,所有应用线程会被暂停,标记出从 GC Root 开始直接可达的对象。
  2. G1 执行的第二阶段:并发标记
    从 GC Roots 开始,对堆中对象进行可达性分析,找出存活对象,耗时较长。
  3. 最终标记
    标记那些在并发标记阶段发生变化的对象,将被回收。
  4. 筛选回收
    首先,对各个 Regin 的回收价值和成本进行排序,根据用户所期待的 GC 停顿时间,来指定回收计划,回收一部分 Region 。

G1 中提供了 Young GC、Mixed GC 两种垃圾回收模式,这两种垃圾回收模式,都是 Stop The World (STW) 的。

浮动垃圾

浮动垃圾(Floating Garbage)是指在G1垃圾回收器中,由于不同区域的垃圾回收行为和策略不同,一些对象可能会部分回收而无法完全回收的情况。产生浮动垃圾的主要原因是由于G1回收器的回收策略是基于每个区域的垃圾量和回收成本来决定的,因此在某些情况下,可能会产生一些浮动垃圾

浮动垃圾产生的过程如下:

  1. 假设某个区域中有些对象,其中一部分已经不再被引用,成为垃圾,另一部分仍然被其他对象引用。
  2. G1回收器执行垃圾回收时,如果优先选择了其他区域进行回收,而没有回收这个区域,那么这个区域中的对象就无法完全回收,即部分对象成为浮动垃圾。
  3. 这些浮动垃圾会继续存留在该区域,因为G1回收器不会对它们进行全面的回收。

如何处理浮动垃圾:G1垃圾回收器在处理浮动垃圾时,会通过周期性的垃圾回收来逐步清理这些垃圾对象,从而避免内存碎片的过度积累。

总结:由于G1回收器的回收策略是基于每个区域的垃圾量和回收成本来决定的,因此在某些情况下,可能会产生一些浮动垃圾。但是,随着G1回收器的周期性回收和动态调整回收优先级,这些浮动垃圾会逐渐被回收掉,从而避免了内存碎片的过度积累。整体上,G1垃圾回收器通过浮动垃圾回收的机制,可以更高效地管理内存,提高垃圾回收性能。

G1的关键概念

G1可以估计每个Region中的垃圾比例,优先回收垃圾多的Region

跨代引用

一个EDEN的区域中可能没有GC Root与其相连,GC收集器可能会将其当成垃圾回收


但实际上可能是这样的,其他的区域、其他的代可能有指向其的指针,这样就不能安全的回收。

跨代引用是指新生代中存在对老年代对象的引用,或者老年代中存在对新生代的引用

对于跨代引用,G1回收器使用了一种称为"Remembered Sets"的数据结构来跟踪跨代引用。进行垃圾回收时,如果Region1有根对象A引用了Region2的对象B,显然对象B是活的,如果没有Rset,就需要扫描整个Region1或者其它Region,才能确定对象B是活跃的,有了Rset可以避免对整个堆进行扫描。

Card Table
JVM将内存划分成了固定大小的Card。每个card覆盖512字节的内存空间
当对应的内存空间发生改变时,标记为dirty

RSet
RSet全称是Remember Set,每个Region中都有一个RSet,记录的是其他Region中的对象引用本Region对象的关系(谁引用了我的对象)。
每个Region初始化时,会初始化一个remembered set

比如:Region1和Region3中有对象引用了Region2的对象,则在Region2的Rset中记录了这些引用。

G1里面还有另外一种数据结构就Collection Set(CSet),CSet记录的是GC要收集的Region的集合,CSet里的Region可以是任意代的。在GC的时候,对于old->young和old->old的跨代对象引用,只要扫描对应的CSet中的RSet即可

对年轻代的Region。它的RSet只保存了来自老年代的引用,这是因为年轻代的回收是针对所有年轻代Region的。所以年轻代Region的RSet有可能是空的。
而对于老年代的Region来说,它的RSet也只是保存老年代对它的引用。这是因为老年代回收之前,会先对年轻代进行回收。这时,Eden区变空了,而在回收过程中会扫描Survivor分区,所以也没必要保存来自年轻代的引用。
RSet通常会占用很大的空间,大约5%或者更高。不仅仅是空间方面,很多计算开销也是比较大的。

为了解决三色标记中的漏标问题,G1是使用SATB算法
SATB
SATB的全称是Snapchat-At-The_Beginning。SATB是维持并发GC的一种手段。G1并发的基础就是SATB。SATB可以理解成在GC开始之前对堆内存里的对象做一次快照,此时活的对象就认为是活的,从而形成一个对象图。如,此时B.c = C,在快照之后,即使B.c = null,也认为c是活的。
这样在Remark标记阶段可以再次扫描c是否是活的,这里结合Rset实现

G1 的 GC 模式

YoungGC 年轻代收集

在分配一般对象时,发生时机就是Eden区满的时候。

每次 younggc 会回收所有 Eden 、以及 Survivor 区,并且将存活对象复制到 Old 区以及另一部分的 Survivor 区。

  • 在 Young GC 时会对 GC Root 进行初始标记
  • 在老年代占用堆内存的比例达到阈值时,对进行并发标记(不会STW),阈值可以根据用户来进行设定

YoungGC 的回收过程:

  • 根扫描,跟 CMS 类似,Stop the world,扫描 GC Roots 对象,构建Cset;
  • 排空Dirty card Queue,更新 RSet;此阶段完成后,RSet可以准确的反映老年代对所在的内存分段中对象的引用,可以看作是一次补充
  • 扫描 RSet ,识别被老年代对象指向的EDEN区的对象,这些被指向的对象被认为是存活的对象;
  • 复制对象(使用的是复制算法),对象数被遍历, Eden区region中存活的对象会被复制到Surivor区中的region,Survivor region中的存活对象如果年龄未达到阈值,年龄+1, 达到阈值会被复制到Old region, 如果Survivior的空间不够用,Eden中的部分数据会直接晋升到老年代。
  • 处理引用队列、软引用、弱引用、虚引用。最终EDEN区的数据为空,这些空的region将会对等待对象的分配,GC停止工作,GC停止工作。

G1老年代垃圾回收

老年代的垃圾回收,严格上来说其实不算是回收,它是一个并发标记的过程,顺便清理一点点对象

老年代并发标记

  • 当堆空间的内存占用达到阈值(-XX:InitiatingHeapOccupancyPercent,默认45%)就开始老年代的并发标记过程。
  • 初始标记阶段:标记GC Roots直接可达的对象,也就是直接引用关系对象,会发生STW(由于是直接可达的对象的标记,所以暂停时间很短),并且会触发一次Young GC
  • 根区域的扫描(Root Region Scanning):G1扫描Survivor区直接可达的老年代区域对象,并标记被引用的对象。这一个过程必须在Young GC之前完成(因为Young GC会操作Survivor区中的对象)。
  • 并发标记(Concurrent Marking):在整个堆中进行并发标记(与程序线程并发执行),此过程可能会被Young GC打断,在并发标记阶段中,若发现某些region中所有对象都是垃圾,那这个region就会被立即回收,同时并发标记过程中,会计算每个region的对象活性(该region存活对象的比例,G1垃圾回收的时候并不是所有region都会参与回收的,根据回收的价值高低来优先回收价值较高的region)。
  • 再次标记:由于并发标记阶段是收集器的标记线程和程序线程并发执行的,需要进行再次标记,修正上一次的标记结果,可以理解为增量补偿标记。会出现STW(暂停时间较短)。G1采用的是比CMS跟快的初始快照算法:snapshot-at-the-beginning(SATB)
  • 独占清理:计算各个region的存活对象和GC回收比例,并进行排序(回收价值高低排序),识别可以混合回收的区域。为下阶段做铺垫,会发生STW。需要注意的是这个阶段实际上并不会做垃圾的回收。
  • 并发清理阶段:识别并清理完成空闲的区域

mixed gc

真正的清理,发生在"混合模式",它不只清理年轻代,还会将老年代的一部分区域进行清理

混合式垃圾回收,每次收集既可能只收集年轻代分区(年轻代收集),也可能在收集年轻代的同时,包含部分老年代分区(混合-收集),这样即使堆内存很大时,也可以限制收集范围,从而降低停顿

G1有一个参数:“-XX:InitiatingHeapOccupancyPercent”,默认值是45%

也就是说,当老年代的大小占据了堆内存的45%的Region时,此时就会触发一个新生代和老年代的混合回收阶段,对所有区域进行全面回收。

当越来越多的对象晋升到老年代 old region 时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即 mixed gc ,该算法并不是一个 old gc ,除了回收整个 young region ,还会回收一部分的 old region 。
这里需要注意:是一部分老年代,而不是全部老年代,可以选择哪些 old region 进行收集,从而可以对垃圾回收的耗时时间进行控制。

问:为什么有的老年代被拷贝了,有的没拷贝?
因为指定了最大停顿时间,如果对所有老年代都进行回收,耗时可能过高。为了保证时间不超过设定的停顿时间,会回收最有价值的老年代(回收后,能够得到更多内存)

G1 没有 full GC 概念,需要 full GC 时,调用 serialOldGC 进行全堆扫描(包括 eden、survivor、o、perm)。

适用场景:

  • 同时注重吞吐量和低延迟(响应时间)
  • 超大堆内存(内存大的),会将堆内存划分为多个大小相等的区域
  • 整体上是标记整理算法,两个区域之间是复制算法

相关参数:JDK8 并不是默认开启的,所需要参数开启

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值