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


相关推荐
该用户已不存在1 分钟前
Rust Web框架大比拼:Actix vs Axum vs Rocket,别再只看跑分了
后端·rust
OneWind14 分钟前
使用CloudFlare R2上传图片慢怎么解决
后端
River41617 分钟前
Javer 学 c++(十六):对象特性篇(上)
c++·后端
文心快码BaiduComate24 分钟前
轻松实践:用Python实现“名字大作战”游戏,表白Zulu!
前端·后端·微信小程序
bobz96524 分钟前
tc 的锁问题
后端
空想兔27 分钟前
JeecgBoot SkyWalking 分布式链路跟踪配置
后端·elasticsearch
sunbin44 分钟前
稀土掘金我要吐槽你
后端
程序员鱼皮2 小时前
我代表编程导航,向大家道歉!
前端·后端·程序员
zjjuejin2 小时前
Maven 生命周期与插件机制
后端·maven
阿杆3 小时前
为什么我建议你把自建 Redis 迁移到云上进行托管
redis·后端