深入理解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的回收阶段,继续这场"探秘之旅"!


相关推荐
codingandsleeping3 小时前
浏览器的缓存机制
前端·后端
追逐时光者4 小时前
面试官问:你知道 C# 单例模式有哪几种常用的实现方式?
后端·.net
Asthenia04124 小时前
Numpy:数组生成/modf/sum/输出格式规则
后端
Asthenia04124 小时前
NumPy:数组加法/数组比较/数组重塑/数组切片
后端
Asthenia04124 小时前
Numpy:limspace/arange/数组基本属性分析
后端
Asthenia04124 小时前
Java中线程暂停的分析与JVM和Linux的协作流程
后端
Asthenia04124 小时前
Seata TCC 模式:RootContext与TCC专属的BusinessActionContext与TCC注解详解
后端
自珍JAVA4 小时前
【代码】zip压缩文件密码暴力破解
后端
今夜有雨.5 小时前
HTTP---基础知识
服务器·网络·后端·网络协议·学习·tcp/ip·http
Asthenia04125 小时前
Seata TCC 模式的空回滚与悬挂问题之解决方案-结合时序分析
后端