深入理解G1垃圾回收器:GC Roots与标记机制的“人性化”探秘


深入理解G1垃圾回收器:GC Roots与标记机制的"人性化"探秘

G1(Garbage-First)垃圾回收器是Java世界的"清道夫",而GC Roots和标记机制则是它的"灵魂"。想知道JVM如何判断哪些对象该留、哪些该扔?跟着我一起走进这场垃圾回收的"侦探游戏",从GC Roots的"根源"到标记的"追踪术",带你轻松get核心!

一、GC Roots的本质:垃圾回收的"生命线"

GC Roots是垃圾回收的"根集合",就像一棵参天大树的根,所有的"活对象"都得靠它们抓住生命线。理解GC Roots的精髓,就是搞清楚哪些引用是从"堆外"伸进来的"救命稻草"。

先从反面入手:什么不是GC Roots?

有时候,排除法比直接定义更直观。我们先把"伪装者"揪出来:

  • 堆里的普通对象 :比如new String(),它得有人"罩着"才能活。
  • 没人管的孤儿:没被任何根引用的对象,注定被回收。
  • 非静态成员变量:这些是对象的"跟班",自己没资格当根。
  • 挂掉的线程:线程一结束,它手里的东西也"随风而去"。

GC Roots的"四大金刚"

筛选掉杂牌军后,真正的"根"现身了,它们是:

  1. 线程栈上的引用

    • 包括:方法里的局部变量、参数、临时变量。
    • 特点:像临时工,方法调用栈一变,它们就"下岗"。
    • 例子void foo() { User user = new User(); } ------ user是个根,但方法一结束,它就"下班"了。
  2. 静态变量与常量

    • 包括static字段、final常量。
    • 特点:像老员工,和类"同生共死"。
    • 例子static Config globalConfig; ------ 只要类在,它就在。
  3. JNI引用

    • 包括:Native代码(比如C++)里抓着的Java对象。
    • 特点:跨语言的"外交大使",地位特殊。
    • 例子JNIEnv->NewObject() ------ 从C世界伸来的"援手"。
  4. 系统保留对象

    • 包括 :类对象(Class)、活跃线程、系统类加载器。
    • 特点:JVM的"核心资产",少了谁都不行。
    • 例子Object.classThread.currentThread() ------ 这些是大佬级别。

这些"根"就像城市的"水源地",所有的对象都得通过它们"喝到水"才能存活。

二、初始标记:快如闪电的"起跑枪"

初始标记是G1的第一个"暂停"(STW),就像赛跑前的发令枪,目标是迅速标记GC Roots的"直系亲属"。

它有多"快刀斩乱麻"?

  • 只扫GC Roots直接抓着的对象,至于"七大姑八大姨",留给后面处理。
  • 不追踪完整的引用链,效率优先。
  • 经常搭Young GC的"顺风车",省时又省力。

举个小例子

java 复制代码
class Graph {
    static Node root = new Node(); // GC Root
    
    static class Node {
        Node next;
        Node() { next = new Node(); }
    }
}

初始标记只管root,至于root.next或者更远的"亲戚",它才不费那个心思呢。

为什么这么快?

  • 暂停时间:通常1ms以内,快得像眨眼间的事。
  • 并行加速:多线程一起上,像"扫地僧"集体出动。
  • 位图妙招:用Bitmap记下标记状态,效率高得飞起。

三、并发标记:织出一张"存活大网"

初始标记只是开了个头,接下来是并发标记------它像个"名侦探",从GC Roots出发,顺着线索追踪,画出完整的"存活地图"。

它的"侦探术"有哪些?

  1. 顺藤摸瓜

    • 从GC Roots开始,深度优先搜索,把所有能找到的"活物"标记出来。
  2. SATB算法(时间快照)

    • 在标记开始时拍个"全家福",保证画面不乱。
    • 用写屏障(Write Barrier)记下中途的"捣乱者",确保不漏线索。
  3. 脏卡记事本

    • 引用变了?记在"脏卡表"(Card Table)里,回头再查。

幕后工具箱

工具名 干啥用的? 咋实现的?
标记位图 标记谁还活着 每个Region一张"名单"
标记栈 存着待查的线索 像个"待办清单"
脏卡表 记下中途的"变动" 字节数组当"记事本"

这阶段不打扰程序,像个默默工作的"助手",一边标记一边让应用继续跑。

四、重新标记与清理:查漏补缺的"收尾大师"

重新标记:再来一次"体检"

并发标记难免有"漏网之鱼",重新标记就暂停一下(STW),处理脏卡里的变化,补上漏标、修好错标,确保万无一失。

清理:收拾"战场"

  1. 摸底存活率:看看每个Region还有多少"活口"。
  2. 组回收队:挑出要清理的区域(CSet)。
  3. 更新通讯录:刷新跨区域引用的"记账本"(Remembered Set)。
  4. 大扫除:完全没用的Region直接"清空"。

五、实战小妙招:轻松记住GC Roots

口诀助攻

"栈静JNI系统"(读起来像"站静JNI系统"):

  • :线程栈引用
  • :静态变量
  • JNI:本地方法引用
  • 系统 :系统保留对象
    念几遍,保证过目不忘!

避坑小贴士

  • 误区 :所有线程都是GC Roots?
    真相:只有活线程算,挂了的线程不算数。
  • 误区 :静态变量引用的对象是GC Root?
    真相:静态变量是根,它指向的对象只是"被罩着"而已。

六、总结:G1标记的"精髓提炼"

  1. GC Roots是命根子:四大类型,从堆外来的"定海神针"。
  2. 标记分步走:初始标记快如风,并发标记织大网,重新标记补漏洞。
  3. 技术亮点:SATB锁住一致性,写屏障撑起并发。
  4. 效率秘诀:用最短的暂停换最大的成果。

搞懂这些,你就能"摸透"G1的脾气,下次调优时胸有成竹。垃圾回收原来也可以这么有趣,对吧?下回我们再聊聊G1的回收阶段,继续这场"探秘之旅"!


相关推荐
颜淡慕潇几秒前
Spring Boot 3.3.x、3.4.x、3.5.x 深度对比与演进分析
java·后端·架构
布列瑟农的星空几秒前
WebAssembly入门(一)——Emscripten
前端·后端
小突突突1 小时前
Spring框架中的单例bean是线程安全的吗?
java·后端·spring
iso少年1 小时前
Go 语言并发编程核心与用法
开发语言·后端·golang
掘金码甲哥2 小时前
云原生算力平台的架构解读
后端
码事漫谈2 小时前
智谱AI从清华实验室到“全球大模型第一股”的六年征程
后端
码事漫谈2 小时前
现代软件开发中常用架构的系统梳理与实践指南
后端
Mr.Entropy2 小时前
JdbcTemplate 性能好,但 Hibernate 生产力高。 如何选择?
java·后端·hibernate
YDS8292 小时前
SpringCloud —— MQ的可靠性保障和延迟消息
后端·spring·spring cloud·rabbitmq
无限大63 小时前
为什么"区块链"不只是比特币?——从加密货币到分布式应用
后端