JVM G1 垃圾回收器

原理

G1 垃圾回收有两种方式

  1. 年轻代回收
  2. 混合回收

总体流程

  1. 新创建的对象会放在 Eden 区,当 G1 判断年轻代内存空间不足后(默认 60%),会发起 Young GC
  2. 标记 Eden + Survivor 区的存活对象
  3. 根据配置的最大暂停时间选择某些区域将存活对象复制到新的 Survivor 中(年龄 + 1),清空这些区域
  4. 后续 Young GC 与上面步骤相同
  5. 当某个对象年龄达到阈值后,会晋升到老年代
  6. 对象大小如果超过了 Region 的一半,会直接放入老年代,这种老年代称为 Humongous(可以横跨多个 Region 专门用于存放大对象)
  7. 多次回收之后会出现很多老年代区,当总堆占有率达到阈值时(默认 45%),会触发Mixed GC,回收所有年轻代以及部份老年代的对象(包括大对象区),该过程使用复制算法完成

年轻代回收

记忆集-RSet(Rembered Set)

年轻代回收只扫描,年轻代对象(Eden + Survivor),所以 GC Roots 引用的年轻代对象或者年轻代引用了其他的年轻代对象都很容易扫描出来,

但是如果是老年代的对象引用年轻代的对象则不那么好扫描,如果逐个遍历的话效率太低,所以使用 RSet,RSet 记录了非收集区域对象引用了收集区域对象的映射关系,扫描时将 RSet 中的对象加入到 GC Roots 中就能根据引用链判断哪些对象需要回收

流程

将内存划分为很多区域,每个区域都有对应的编号

上图中 A、B、C 都引用了年轻代对象,RSet 只记录他们的区域号,不直接记录他们的具体地址,减少内存消耗,这个区域号就是卡表

卡表(Card Table)

每一个 Region 都拥有自己的卡表,如果产生了跨代引用(老年代引用年轻代),此时这个 Region 对应的卡表会将字节内容进行修改,0 代表引用了年轻代对象称为脏卡,这样就可以标记出当前 Region 被老年代中的哪些部分引用了,那么要生成记忆集就很简单了,只需要遍历整个卡表找到所有脏卡

流程

Region1 的卡表中有整个内存的所有区域编号,当老年代 Region2 引用了年轻代 Region1 的对象后,Region1 的卡表会将 Region2 中对应的区域编号标记为 0,表明该区域有对象引用了 Region1 的对象

建立记忆集时,通过扫描卡表就能快速的知道 Region2 的 20 区域有对象引用了年轻代对象

如上图,当记忆集中记录了 Region2 有跨代引用后,Young GC 时会将对应的 A、B 对象放入到 GC Roots 中并对他们进行扫描,标记引用链上的对象

内存占用

卡表与堆大小的比例为 1:512

例如 堆大小 = 1G,卡表 = 1024MB / 512 = 2MB

写屏障

JVM 通过写屏障,在建立引用关系的时候,可以在代码前和代码后插入一段指令,从而维护卡表

记忆集中不会记录新生代引用新生代,也不会记录同一个 Region 的引用

总结

生成记忆集分为几个步骤

  1. 通过写屏障获得引用变更的信息
  2. 将引用关系记录到卡表中,并记录到一个脏卡队列
  3. JVM 会创建一个定时线程定期从脏卡队列中获取数据,生成记忆集(不直接写入记忆集是为了避免过多线程并发访问记忆集)

年轻代回收流程

以下整个过程都是 STW

  1. GC Roots 扫描,将所有静态变量、局部变量扫描出来
  2. 处理脏卡队列中没有处理完的数据,更新记忆集的数据,处理完成后,记忆集中包含了所有老年代引用年轻代的的引用关系
  3. 标记存活对象,记忆集中的对象会加入到 GC Roots 中,GC Roots 引用链上的对象会被标记为存活对象
  4. 根据设置的最大停顿时间,选择本次收集的区域,称之为回收集合
  5. 复制对象,将标记的对象复制到新区域中,年龄 + 1,如果年龄达到阈值后则晋升老年代,老的区域直接清空
  6. 处理弱、软、虚等引用

混合回收

当总堆占有率达到阈值时(默认 45%),会触发Mixed GC

触发时机

混合回收由年轻代回收或者大对象分配之后触发

回收范围

混合回收会回收整个年轻代 + 部分老年代

步骤

  1. 初始标记,STW,采用三色标记法标记 GC Roots 直达的对象
  2. 并发标记,与用户线程并发执行,对存活对象进行标记
  3. 重新标记,STW,处理 SATB 相关对象的标记
  4. 清理,STW 如果区域中没有存活对象就直接清空该区域
  5. 转移,将存活对象复制到其他区域
初始标记

初始标记使用三色标记

灰色通过灰色队列进行存储

黑色与白色通过位图进行标识,内存中的 8 字节对应位图中的 1 bit,如下图中,8 字节大小的对象对应 1 个 bit,16 字节的对象则对应 2 个bit(10),位图中 1 代表黑色,0 代表白色

并发标记
漏标

三色标记会存在漏标的情况

如何解决漏标

G1 中使用了 SATB 技术解决漏标问题

最终标记

注意:即使上图中的 C 对象在并发标记时被 B 断开了引用,也没有被 A 引用,此时处于未被引用状态,也不会被回收,而是存活到下一轮 GC

转移

转移的过程其实是进行复制,并不是移动对象

相关推荐
右耳朵猫AI1 小时前
JavaScript技术周刊 2026年第20周
开发语言·javascript·ecmascript
摇滚侠1 小时前
浏览器调试工具 检查元素 谷歌模拟器 控制台 断点调试
java·html
心之伊始2 小时前
Spring Boot 接入 MCP 实战:用 Spring AI 调用本地工具的最小闭环
java·spring boot·agent·spring ai·mcp
basketball6162 小时前
Go 语言从入门到进阶:5. 玩转Go函数
开发语言·后端·golang
Refrain_zc2 小时前
无触摸屏场景下的蓝牙交互:Android 纯按键蓝牙扫描配对与 A2DP/Headset 连接
java·蓝牙
多彩电脑2 小时前
Kivy如何自定义事件
开发语言·python
java_cj2 小时前
LangChain初入门 - 简化LLM开发难度的利器
开发语言·python·langchain
计算机安禾2 小时前
【算法设计与分析】第29篇:启发式与元启发式搜索方法综述
java·数据库·算法