【JVM原理详解】22-GC-Roots详解
22-GC Roots 详解上一篇讲到可达性分析从一组确定存活的根对象出发去遍历整张对象图。这组根对象就是GC Roots。但到底哪些对象能成为 GC Roots这并不是一句栈里的引用就能说清楚的——HotSpot 实际定义了多类根对象每一类背后都对应着 JVM 的某个内部结构。本篇将逐一拆解 GC Roots 的种类并演示如何用工具去实际观察它们。GC Roots 的种类理解 GC Roots关键在于明白一个原则GC Roots 是虚拟机规范层面上不得回收的引用来源。只要引用还存在于这些位置被引用的对象就不能被回收。HotSpot 将 GC Roots 分为以下几大类。1. 虚拟机栈中的局部变量这是最直观、也是数量最多的一类。当前所有线程的 Java 方法栈帧中局部变量表里存放的对象引用都是 GC Roots。publicvoidfoo(){ObjectlocalnewObject();// local 就是 GC Roots// 方法返回前local 指向的对象不可回收}每个栈帧的 LocalVariableTable 中类型为reference的槽位都会作为根。这也是为什么大方法里临时引用过多会增加标记开销——根越多初始标记集合越大。2. 方法区中的静态属性类的静态字段引用的对象同样属于 GC Roots。只要类本身没被卸载这些对象就一直可达。publicclassConfigHolder{// staticConfig 是 GC RootspublicstaticfinalConfigSTATIC_CONFIGloadConfig();}JDK 8 之后方法区由**元空间Metaspace**实现静态字段存储在 Class 对象关联的镜像mirror上。即使如此它们仍属于方法区层面的根。3. 方法区中的常量常量池里的引用类型常量也是 GC Roots。例如字符串常量池String Table中的String对象publicclassConstExample{// HELLO 进入字符串常量池成为 GC RootpublicstaticfinalStringGREETINGHELLO;publicvoiduse(){// 运行期 intern 的字符串也会进入常量池StringsnewString(world).intern();}}JDK 7 之后字符串常量池从永久代移到了 Java 堆中但其作为 GC Roots 的属性没有改变。4. 本地方法栈中的 JNI 引用通过 JNI 调用本地代码时本地方法栈中保存的对象引用也是 GC Roots。比如NewGlobalRef、NewLocalRef创建的引用。// JNI 代码示例JNIEXPORTvoidJNICALLJava_Cache_setHandle(JNIEnv*env,jobject this){jobject globalRef(*env)-NewGlobalRef(env,this);// globalRef 进入本地方法栈成为 GC Root// 必须显式 DeleteGlobalRef 才能被回收}这类根容易造成内存泄漏本地代码持有 Java 对象的全局引用但忘记释放对象就永远无法回收。5. Java 虚拟机内部的引用JVM 自身运行需要持有一些对象它们也是 GC Roots包括基本类型的 Class 对象Integer.class等常用的异常对象NullPointerException、OutOfMemoryError等类加载器对象JIT 编译器内部持有的对象这部分通常数量不大但不可或缺。6. 同步锁monitor持有的对象synchronized锁住的对象在锁被释放前不会被回收。因为持有锁的线程通过对象头中的 Monitor 记录与之关联。publicclassLockRoot{privatefinalObjectlocknewObject();publicvoiddoWork(){synchronized(lock){// lock 在 synchronized 块内是 GC Root// 即使没有其他引用也不会被回收waitForEvent();}}}7. JMXBean、JVMTI 等管理接口引用JVM 通过 JMX 暴露的 MBean、JVMTIJVM Tool Interface持有的对象也会作为 GC Roots。调试器、profiler 通过 JVMTI 跟踪的对象在调试/profiling 期间不会被回收这是开发期常见的对象该死却没死原因之一。全局根与局部根可以把上述 GC Roots 简单分为两类局部根随线程生灭如栈帧局部变量、JNI local ref。全局根JVM 范围共享如静态字段、常量池、JVM 内部引用、JMX 等。GC 开始时HotSpot 会遍历所有线程的栈帧、JNI 引用再扫描全局根集合形成完整的初始根集合。用 jmap 查看 GC Rootsjmap适合在 JDK 8 下使用它提供-finalizerinfo和堆直方图等视图。要查看根引用更常用的是jmap -dump导出堆转储后用 MAT 分析。# 导出堆转储jmap -dump:live,formatb,fileheap.hprofpid# live 参数会先触发一次 GC仅保留存活对象MAT 打开后在 “Leak Suspects” 或 “Histogram” 视图中可以查看任意对象的“GC Roots”路径它会告诉你这个对象是从哪条引用链到达的。JDK 9 推荐改用jcmdjcmdpidGC.heap_dump heap.hprofjmap -histo 观察对象统计# 按对象大小排序jmap-histopid|head-20# 只看存活对象会触发 GCjmap-histo:livepid|head-20输出示例num #instances #bytes class name ---------------------------------------------- 1: 245632 19705632 [C 2: 89324 7145920 java.lang.String 3: 45211 3616880 java.util.HashMap$Node虽然这只给出直方图不直接显示根但配合live参数可以快速验证哪些对象被某种根引用所持有。用 Arthas 查看 GC RootsArthas 是阿里开源的诊断工具提供了heapdump命令与jmap类似# 下载并启动 arthasjava-jararthas-boot.jarpid# 导出堆转储[arthas1234]$ heapdump--live/tmp/heap.hprof# 查看对象大小[arthas1234]$ dashboardArthas 的heapdump同样会生成 hprof 文件可丢给 MAT 分析。它的优势在于无需提前安装、可 attach 到运行进程上。用 Arthas 观察静态字段根Arthas 的ognl命令可以直接读取类的静态字段配合heapdump能快速定位为什么这个对象不释放# 查看某静态字段引用的对象[arthas1234]$ ognlcom.example.ConfigHolderSTATIC_CONFIGMAT 中的 GC Roots 分类打开 hprof 后在 MAT 中查看某个对象的 “Path To GC Roots”会看到根被标注为以下类型之一Java local栈帧局部变量Static field静态字段JNI globalJNI 全局引用JNI localJNI 局部引用Monitor被 synchronized 持有Java frame栈帧旧分类System class系统类加载的静态字段Thread线程对象本身Busy Monitor被某个线程持有锁的对象这些分类与上文列出的 GC Roots 种类一一对应。代码示例观察 GC Roots 的引用链下面通过一段代码构造不同类型的 GC Roots再用堆转储观察引用链/** * 构造多种 GC Roots便于在 MAT 中观察 * 适用 JDK 8/11/17 */publicclassGcRootsDemo{// 1. 静态字段根privatestaticfinalObjectSTATIC_ROOTnewBigObject(static);// 2. 常量根publicstaticfinalStringCONST_ROOTGC_ROOT_CONSTANT;// 3. 同步锁根privatestaticfinalObjectLOCKnewBigObject(lock);publicstaticvoidmain(String[]args)throwsException{// 4. 局部变量根BigObjectlocalnewBigObject(local);// 让 LOCK 一直被持有newThread(()-{synchronized(LOCK){try{Thread.sleep(Long.MAX_VALUE);}catch(InterruptedExceptione){Thread.currentThread().interrupt();}}},lock-holder).start();// 5. JNI 全局引用模拟通过 Thread 持有ThreadholdernewThread(()-{ObjectjniLikenewBigObject(jni-like);try{Thread.sleep(Long.MAX_VALUE);}catch(InterruptedExceptione){Thread.currentThread().interrupt();}},jni-holder);holder.setDaemon(true);holder.start();// 留出时间导出堆Thread.sleep(60_000);}staticclassBigObject{privatefinalbyte[]datanewbyte[512*1024];privatefinalStringtag;BigObject(Stringtag){this.tagtag;}}}用jcmd pid GC.heap_dump heap.hprof导出后在 MAT 中打开按BigObject类型过滤逐一查看每个实例的 “Path To GC Roots”static标签的对象 →Static fieldGcRootsDemo.STATIC_ROOTlock标签的对象 →Busy Monitorlock-holder线程持有local标签的对象 →Java localmain线程栈帧jni-like标签的对象 →Threadjni-holder线程引用这种实操能让你对GC Roots 是什么形成直觉。实践要点1. 静态集合是常见的隐性根static MapString, Object CACHE new HashMap();这种写法在工程里非常常见。如果只是put不remove缓存中的对象就会因静态字段根而永生。建议用WeakHashMap或带 TTL 的缓存替代。2. ThreadLocal 与 GC RootsThreadLocal的值实际存储在线程对象的ThreadLocalMap中而线程对象是 GC Root。所以线程不死ThreadLocal 中的对象不回收。线程池场景下尤其要主动remove()。3. JNI 泄漏最难排查本地代码持有全局引用未释放导致的泄漏MAT 中会看到根类型是JNI global。这种泄漏需要回到 C/C 代码侧审计NewGlobalRef/DeleteGlobalRef配对。4. 调试器/profiler 会改变可达性线上用调试器 attach 时被调试器观察的对象会成为 GC Roots可能掩盖内存泄漏。排查内存问题应尽量用离线堆转储而不是在线 attach。5. 元空间类卸载静态字段成为根的前提是类本身还活着。一旦类被卸载其静态字段引用的对象就不再是根。旧版本 JDK 中自定义类加载器泄漏常因类加载器间的引用链导致类无法卸载引起。小结GC Roots 包括虚拟机栈局部变量、方法区静态属性、方法区常量、本地方法栈 JNI 引用、JVM 内部引用、同步锁持有对象、JMX/JVMTI 引用等。局部根随线程生灭全局根 JVM 范围共享。jmap/jcmd导出堆转储后用 MAT 观察 “Path To GC Roots”可看到根的类型分类。工程中最常见的隐性根是静态集合和 ThreadLocal应作为代码审查的重点。JNI 全局引用、调试器 attach 是对象该死不死的隐蔽原因。下一篇我们继续聚焦引用这个主题深入 Java 的四种引用类型——强、软、弱、虚——它们让你可以在GC Roots 可达之外更精细地控制对象的生命周期。更多内容JVM调优实战

相关新闻

ClearerVoice-Studio语音处理引擎架构深度解析:多模态融合与实时噪声抑制技术实现

ClearerVoice-Studio语音处理引擎架构深度解析:多模态融合与实时噪声抑制技术实现

ClearerVoice-Studio语音处理引擎架构深度解析:多模态融合与实时噪声抑制技术实现 【免费下载链接】ClearerVoice-Studio An AI-Powered Speech Processing Toolkit and Open Source SOTA Pretrained Models, Supporting Speech Enhancement, Separation, and Targe…

2026/10/9 20:34:07 阅读更多 →
Kimi 长文本稳赢、豆包搜索翻车?国产三强在 Taotoken 平台的 4 维实测报告

Kimi 长文本稳赢、豆包搜索翻车?国产三强在 Taotoken 平台的 4 维实测报告

三大模型实战横评:Kimi、豆包、通义千问在Taotoken平台的技术角力 上个月用Taotoken同时接入Kimi、豆包和通义千问API进行深度测试时,我们意外发现三家大模型的技术特性差异远超预期——有的模型在专业领域表现惊艳却在基础功能上漏洞百出。经过72小时高…

2026/10/13 21:35:50 阅读更多 →
HarmonyOS应用开发实战:猫猫大作战-随机等级生成算法

HarmonyOS应用开发实战:猫猫大作战-随机等级生成算法

前言 在「猫猫大作战」中&#xff0c;新投放的猫咪需要随机生成等级&#xff0c;但不能全是低级猫也不能全是高级猫。我们使用加权随机算法控制各等级的出现概率。 一、加权随机 private randomLevel(): CatLevel {const rand Math.random();if (rand < 0.5) return Cat…

2026/10/12 21:14:21 阅读更多 →

最新新闻

windows_exporter net 采集器详解:Windows 网络接口流量、状态与告警的完整实现

windows_exporter net 采集器详解:Windows 网络接口流量、状态与告警的完整实现

可观测性指标监控 【免费下载链接】windows_exporter Prometheus exporter for Windows machines 项目地址&#xff1a; https://gitcode.com/gh_mirrors/wi/windows_exporter 点击查看 免费下载 本文围绕 windows_exporter 的 net 采集器展开&#xff1a;它默认启用&#xff…

2026/10/14 16:42:54 阅读更多 →
Grbl_Esp32 User I/O 完全指南:用 M62–M68 G 代码控制数字与 PWM 模拟输出

Grbl_Esp32 User I/O 完全指南:用 M62–M68 G 代码控制数字与 PWM 模拟输出

嵌入式硬件开发物联网工业制造 【免费下载链接】Grbl_Esp32 A port of Grbl CNC Firmware for ESP32 项目地址&#xff1a; https://gitcode.com/gh_mirrors/gr/Grbl_Esp32 点击查看 免费下载 本文以 Grbl_Esp32 仓库中的 User I/O 说明文档 为主线&#xff0c;系统讲解如何通…

2026/10/14 16:42:54 阅读更多 →
工作效率提升的四大核心策略与陷阱规避

工作效率提升的四大核心策略与陷阱规避

1. 工作效率困境的本质剖析每天早晨打开电脑&#xff0c;面对堆积如山的待办事项&#xff0c;我们常常陷入一种诡异的循环——明明忙得脚不沾地&#xff0c;下班时却发现重要事项毫无进展。这种状态我称之为"虚假忙碌症候群"&#xff0c;其核心特征是&#xff1a;重复…

2026/10/14 16:42:54 阅读更多 →
多条件阈值告警链路:Java与InfluxDB Check协同实践

多条件阈值告警链路:Java与InfluxDB Check协同实践

最先要考虑的问题其实是&#xff1a;多条件阈值判断这块逻辑&#xff0c;到底应该写在 InfluxDB 内部&#xff0c;还是写在你自己的 Java 服务里。这个选择做不好&#xff0c;后面会遇到两类问题&#xff1a;一类是告警判断被写死在业务代码里&#xff0c;时间窗口一改就要发版…

2026/10/14 16:42:54 阅读更多 →
Bandicam录屏软件:游戏与教学录制的高效稳定之选

Bandicam录屏软件:游戏与教学录制的高效稳定之选

简介&#xff1a;这份资源是面向多媒体创作者、游戏玩家与教学演示人员的Bandicam录屏软件压缩包&#xff0c;可解决高清屏幕录制、游戏画面捕捉与音频同步采集等需求。压缩包共45个文件&#xff0c;约36.41MB&#xff0c;以dll动态库、dat数据文件、ini配置、wav音频、exe可执…

2026/10/14 16:42:54 阅读更多 →
AI Agent状态清理:超时重试背后的系统性风险与四维治理

AI Agent状态清理:超时重试背后的系统性风险与四维治理

1. 这不是超时和重试的问题&#xff0c;是状态管理的系统性溃败“给 AI Agent 加超时和重试后&#xff0c;我才发现真正的坑在状态清理”——这句话刚在内部技术群刷出来时&#xff0c;我正盯着一个跑了37分钟、卡在waiting_for_tool_execution状态、内存占用飙升到4.2GB却毫无…

2026/10/14 16:41:53 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起&#xff1a;它到底想解决什么问题第一次看到"AnyPS5"这个标题&#xff0c;我脑子里冒出来的第一个念头是&#xff1a;这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述&#xff1a;Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师&#xff0c;我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性&#xff0c;实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景&#xff0c;也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间&#xff0c;确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中&#xff0c;这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介&#xff1a;基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码&#xff0c;面向计算机相关专业课程设计与期末大作业学生&#xff0c;以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程&#xff0c;…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程&#xff1a;键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化&#xff0c;十个新手有八个栽在"往输入框里填东西"这件事上&#xff1a;要么填不进去&#xff0c;要么填了一半&#xff0c;要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程&#xff1a;阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀&#xff1a;什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面&#xff0c;跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 7:53:34 阅读更多 →