上菜了 , G1 GC 回收器垃圾回收的处理流程放在这了

👈👈👈 欢迎点赞收藏关注哟

首先分享之前的所有文章 >>>> 😜😜😜
文章合集 : 🎁 juejin.cn/post/694164...
Github : 👉 github.com/black-ant
CASE 备份 : 👉 gitee.com/antblack/ca...

一. 前言

G1 回收器的回收循环主要分为3大主要的类型 : 年轻代循环 ,多步骤并行标记循环 ,混合收集阶段。 同时流程外还有一个保护性的 FullGC 同样存在。

而在流程上我会把年轻代再细分一下,来模拟一下当对象逐渐增长情况下,回收的演变。

历史文章 :

# 这一回 ,把 G1 回收器的特性聊通透

先来看张流程图 :

文章目的 :

  • 梳理 G1 GC 整个阶段的大流程
  • 梳理 每个环节的内存变化
  • 不涉及到源码及细节原理

二. 回收的阶段

2.1 第一阶段 : YoungGC (新生代 GC)

  • S1 : 初始时会分配给 5% 大小的 Region 给新生代
  • S2 : G1 从空闲区间里面挑选出区间加入年轻代
  • S3 : 当新生代的体积逐渐变大, Eden 区已经无法扩展 (60% Region)时,则会触发一次 YoungGC.
    • 这个阶段还会计算总量,统计RSet,当前容量等等数据,用于后续整理年轻代
    • 该环节收集的内存是不固定的 ,回收的分区数目也是不固定的
  • S4 : 在这个环节会触发 STW , 通过复制算法收集 Eden 区存活的对象,放入 S0 Survivor
  • S5 : 在这个过程中 , 会交替使用 S0 和 S1 两个幸存区,直到对象或者整体容积达到触发老年代的条件
    • 也就是说每次YGC后都会为对象生成年龄信息,放入一个年龄虚拟表
    • 每一次 YGC 后都会带来新生代分区数目的变化

在这个阶段中,会对 Eden 和 Survivor 区进行回收,清除垃圾对象,添加对象的生命周期。

  • 核心点首先是根集合 ,然后根据根集合处理的对象处理 RSet 的管理关系。
  • TODO : 当然这里还涉及到没有回收的区域的引用关系以及具体的引用实现,这一篇就不细说了。

2.2 第二阶段 :进化到老年代

  • S1 : 随着一轮一轮的 YoungGC , Survivor 的对象越来越多 , 部分对象熬过了多轮 YoungGC
  • S2 : 此时对达到了老年代阈值的对象进行转移,将这部分生命超过 MaxTenuringThreshold 的对象回收到老年代
  • S3 : 在高并发场景或者回收的继续迭代,Survivor 存活的对象超过了其本身空间的50%
  • S4 : 取当前 50% 的年龄对象 ,对大于该年龄的对象进行转移,放入老年代

这其实属于第一阶段,单独做了一层划分,在这阶段老年代对象会不断积累。

阶段总结

由于这个阶段的积累,老年代数据越来越多 ,就会触发混合 GC (Mixed GC ).

这个 GC 阶段分为两个阶段 :

  • 并发标记 👉 下文 2.3 第三阶段部分
    • 用于识别老年代里面的活跃对象 ,判断垃圾对象所占空间以及是否需要回收
  • 垃圾回收 👉 下文 2.4 第四阶段部分
    • 流程基本和新生代一致,区别在于会针对老年代的Region一起回收

好了 ,继续看下面的!!

2.3 第三阶段 : 并发标记周期

到了这个环节 , 老年代的对象也逐渐积累,这个时候就要为混合回收做准备了。 在这个过程中 ,不会阻碍应用程序的执行, 通过 GC Root 对堆中对象进行可达性分析,查找到所有可以回收的对象。

在这个阶段中 ,标记会分为几个阶段 :

初始标记(STW) 👉 根区域标记 👉 并行标记 👉 重新标记(STW) 👉 清理阶段


  • 初始化标记Initial Mark) :会标记出所有的根对象以及直接与根对象关联的对象。 在这过程中,会触发一次 STW , 从而建立一个 SATB 快照 ,用于后续的跟踪。
    • 根对象 : 栈对象 ,全局对象 ,JNI 对象等
    • 特点 :此阶段会直接借用 YGC 标记的结果,将 Survivor 分区作为根
  • 并发标记Concurrent Marking): 当 YGC 触发后 ,如果满足并发标记的条件,则会触发并发标记
    • InitiatingHeapOccupancyPercent : 默认45 , 即当内存分配超过内存总量的 45% 时 ,触发
    • XX:ConcGCThreads : 启动的并发线程的数量 , 每个线程每次只扫描一个分区
    • STW : 该阶段会扫描所有的分区,不需要 STW , 和主线程并发
  • 重新(最终)标记Final Remark) : 当 YoungGC 经历了多轮后,就可能会进入 Remark 重新标记阶段,
    • 目标 : 处理在并发标记期间发生的引用变化,确保标记信息的准确性。
    • 行为 : 该阶段保证了根扫描及存活对象扫描以及完成 ,同时待处理的标记栈为空
    • STW : 这个阶段也会触发STW ,原因在于需要保证引用都被处理(一直在变
  • 清理阶段Cleanup): 在这个阶段会进行选择性的回收
    • 独占清理 : 基于前面的标记信息来计算各个区域的存活数量以及可回收比例,放入排队序列
    • 并发清理 : 识别并且清理完全空闲的区域,这个阶段是并发执行的 , 清理完直接放入空闲区间
    • 并发整理 : 将存活的对象从多个 Region 复制到多个连续的 Region 中,保证空间的连续
    • 其他操作 : 包括 重置RSet交换标记位图
    • 特别注意 : 清理操作并不会清理垃圾对象 !!

这里我直接借用 《JVM G1源码分析和调优 》 来展现整个流程 ,大家可以看看原书,写得很好

  • 重点一 : 可以明确的看到 ,并不是并发标记后就不 YGC 了, 在 MixedGC 前会一直有
  • 重点二 : 并发标记 和 混合回收整个时间周期其实不短

2.4 第四阶段 : 混合阶段垃圾回收

  • IHOP (-XX:InitiatingHeapOccupancyPercent) : 堆内存占用阈值,用于触发混合回收,默认 45%。
  • 不同于 CMS 可以针对老年代设置阈值 , G1 设置的 IHOP 是针对整个堆内存(Region没有明确的分代

混合回收会对新生代 ,老年代 ,以及大对象进行回收。 另外混合回收会触发 STW , 同时触发最终标记阶段,确定哪些对象是存活对象,哪些对象需要回收,

由于在之前的过程里面,已经对所有区域的所有对象进行过标记,所以可以很轻松的知道那些区域里面的垃圾比例,也就是之前提到的垃圾密度。 G1 回收器会首先收集垃圾比例较高的阶段。

与新生代回收一样,这个阶段剩余的对象会统一合并到多个区域里面去。

这个阶段的操作和 YGC 基本一致,这里就不细说了。

2.5 最终阶段 : FullGC

这个阶段并不是绝对执行,当吞吐量过大或者系统问题导致堆内存不足时,就会触发一次 FullGC.

FullGC 阶段和其他的垃圾回收器一样,也会 STW 同时对所有的区域进行清理。

  • S1 : 对象分配失败 ,进入 Evac 阶段
  • S2 : 尝试再次分配内存 ,如果分配失败 ,则进入 FullGC

这一块其实很复杂,涉及到 RSet的处理,一篇肯定说不清楚,关键是我也没完全看懂,总结不出来啊!

实在想了解的可以关注或者直接看书。

三. 关于回收过程中的标记

3.1 关于 Survivor 空间

谈到 G1 回收器的幸存者区的时候,通常会提到四个值 : S0 Survivor SpaceS1 Survivor Space 以及 From Survivor SpaceTo Survivor Space

那么他们有什么关系呢 ?

S0 Survivor Space 和 S1 Survivor Space 可以理解为一种物理上的概念 , 当对象创建的时候,如果 Eden 满了触发 YoungGC , 一般收集完成后,把存活的对象放在 S0 区域中。而在下一次垃圾回收的过程中 ,由于 S0 也会被回收, 此时就会把存活的对象放到 S1 区域中。从而实现两个幸存区的交替使用。

而 From 和 To 开头的幸存区则是一种逻辑概念。

  • From Survivor Space 是上一轮垃圾回收结束后被用作存放存活对象的Survivor区
  • To Survivor Space 是当前垃圾回收正在使用的Survivor区。

例如当前被回收的是 S0 , 当前空着的是 S1 . 我们会回收完成后把存活对象放在 S1 中。在这个场景下 : S0 = From , S1 = To.

3.2 关于大对象

G1 为大对象引入了一个大对象区间,当一个对象超过一个 Region 的 50% 时 , 就会被认定为大对象。 G1 对于这些大对象不会放在老年代,而是放在 老年代之外大对象区间 里面。

大对象区间通常是多个连续的 Region 区间 。而在回收时,年轻代回收,混合回收, FullGC 都会对大对象区间进行回收工作。

补充的问题 :

就像上文说的并发标记实际上是从 YGC 的结果作为根的 。 当时很多场景下 ,老年代的对象并没有被年轻代的引用.

这种情况下就会导致对象被漏标 ,所以在这阶段 ,要把直接从根出发到老生代的引用或者大对象分区的引用补上。

总结

这一块勉强算是弄清楚了 ,下一篇就针对其中的一些重点进行梳理了 ,膜拜大佬。

整个系列会很长,包括 G1 , ZGC 等多个系列,欢迎关注。

很多东西看的时候也是一知半解,如果有问题欢迎指出。

参考文档

👍 《JVM G1源码分析和调优》

《深入Java虚拟机:JVM G1GC的算法与实现》

《深入理解JVM & G1 GC》

zhuanlan.zhihu.com/p/405142523

我又懂了更多G1的GC大道理 - 知乎 (zhihu.com)

一次垃圾回收的革新------了解G1收集器 - 知乎 (zhihu.com)

相关推荐
spionbo9 分钟前
Vue 表情包输入组件实现代码及完整开发流程解析
前端·javascript·面试
千|寻10 分钟前
【画江湖】langchain4j - Java1.8下spring boot集成ollama调用本地大模型之问道系列(第一问)
java·spring boot·后端·langchain
天涯学馆23 分钟前
前后端分离的 API 设计:技术深度剖析
前端·javascript·面试
程序员岳焱24 分钟前
Java 与 MySQL 性能优化:MySQL 慢 SQL 诊断与分析方法详解
后端·sql·mysql
龚思凯30 分钟前
Node.js 模块导入语法变革全解析
后端·node.js
天行健的回响33 分钟前
枚举在实际开发中的使用小Tips
后端
wuhunyu38 分钟前
基于 langchain4j 的简易 RAG
后端
techzhi38 分钟前
SeaweedFS S3 Spring Boot Starter
java·spring boot·后端
异常君1 小时前
Spring 中的 FactoryBean 与 BeanFactory:核心概念深度解析
java·spring·面试
写bug写bug2 小时前
手把手教你使用JConsole
java·后端·程序员