【大白话说Java面试题 第69题】【JVM篇】第29题:GC Roots 有哪些?

📌 PDF :大白话说Java面试题 --- 02-JVM篇

第29题:GC Roots 有哪些

📚 回答:

  • 核心考点
    GC Roots 是可达性分析算法 的起点。大厂面试要求准确列出所有类型的GC Roots ,并能解释为什么某些对象会成为GC Root 以及常见的误解(如静态变量是否一定能阻止GC?)。

1. GC Roots 的完整定义

GC Roots 是指必须存活的对象集合 ,从它们出发可达的对象被标记为存活 ,不可达的则判定为可回收


2. GC Roots 的5种类型(JDK 8及之前)
类型 说明 示例
虚拟机栈引用 每个栈帧中的局部变量表引用的对象 方法内的局部变量、参数
静态属性引用 方法区中类的静态变量引用的对象 private static User user = new User();
常量引用 方法区中运行时常量池引用的对象 字符串常量 "abc"、Class对象
JNI引用(Native Stack) 本地方法栈中JNI(全局/局部)引用的对象 JNIEnv->NewObject(...)
活跃线程 所有正在运行的Thread对象 Thread.currentThread()

3. 各类型的深度解释(面试加分项)

3.1 虚拟机栈中的引用(最常用)

  • 包括:局部变量、方法参数、临时变量

  • 示例:

    java 复制代码
    void foo() {
        Object o = new Object();  // o 是GC Root
        int i = 0;                // 基本类型不是引用,不算
    }
  • 注意:方法执行完出栈后,这些GC Root消失,对应对象变为不可达。

3.2 静态属性引用

  • 属于类级别,类未被卸载则静态变量一直存活。

  • 常见误解 :设置 static obj = null 后,原对象不再被GC Root引用,可能被回收。

  • 示例:

    java 复制代码
    class Cache {
        static Map<String, Object> map = new HashMap<>();  // map 是GC Root
    }
  • 内存泄漏高发地:静态集合类添加对象后忘记清理。

3.3 常量引用

  • 字符串常量池中的对象(如 "hello"
  • Class对象(如 String.class
  • 基本类型包装类常量(如 Integer.valueOf(1) 缓存的 -128~127 对象)
  • 注意 :常量池中动态添加的字符串(intern())也是GC Root,直到JVM回收该常量。

3.4 JNI 引用

  • Global JNI Reference :显式 NewGlobalRef 创建,需手动 DeleteGlobalRef 否则泄漏
  • Local JNI Reference:本地方法栈帧内的引用,方法退出后自动释放
  • 场景:Android NDK / JNI 开发中,忘记删除GlobalRef导致内存泄漏。

3.5 活跃线程

  • 每个正在运行的线程本身就是GC Root(线程栈+程序计数器)
  • 线程内部的局部变量也是从该线程Root可达的

4. 其他隐藏的 GC Roots(JDK 8+)
类型 说明
系统类加载器 加载核心类(rt.jar)的BootClassLoader
JVM内部对象 SystemDictionaryJVMTI 标记的对象
同步监视器 synchronized锁住的对象
StackMapTable JVM内部栈映射表引用的对象
Finalizer引用 尚未执行finalize()的对象会被Finalizer队列持有

5. 关键区别:哪些不是 GC Roots?
对象 是否是GC Root 原因
方法内的局部变量(未执行到) 栈帧未入栈,不存在引用
不可达的静态变量 类已被卸载(如自定义ClassLoader卸载时)
普通对象字段 需要通过GC Root链到达
软/弱/虚引用指向的对象 引用本身特殊处理,但引用的目标对象需链到Root才算存活

6. 大厂面试追问

Q1:静态变量引用的对象一定是 GC Root 吗?

A:是的,只要该类未被卸载。在自定义ClassLoader场景中,类可被卸载(如OSGi、热部署),卸载后静态变量不再作为GC Root。

Q2:字符串常量池中的对象永远不会被回收吗?

A:不会。JDK 7+字符串常量池在堆中,Full GC时若常量无引用,可以被回收。例如:String s = "a"; s = null; 触发Full GC后 "a" 可能被回收。

Q3:ThreadLocal 中的变量是 GC Root 吗?

A:ThreadLocalMapkey 是弱引用,不是直接GC Root ;但当前Thread对象是GC Root,从Thread可以访问到ThreadLocalMap,进而访问到key。所以只要线程活着,ThreadLocalMap的引用链就不断。

Q4:GC Roots 的数量一般是多少?

A:大应用中可达数万 甚至更多(每个栈帧的局部变量、每个静态变量、每个活跃线程等)。GC Roots 过多会导致可达性分析变慢。

Q5:可达性分析和引用计数法的区别?

A:引用计数无法解决循环引用(A->B, B->A),而可达性分析从GC Roots出发,不可达才回收,能正确处理循环。


7. 实战:如何查看一个对象是否被 GC Root 引用?

工具:MAT(Memory Analyzer Tool)

  1. 打开 heap dump
  2. 选择对象 → Path to GC Roots → 排除软/弱/虚引用
  3. 显示从该对象到某个GC Root的最短路径

示例结果

复制代码
java.lang.Object @ 0x7f3a2c8
→  com.example.Cache.map (静态字段)
→  java.util.HashMap @ 0x7f3a3d0
→  ... (到GC Root)

8. 总结对比表(面试速记)
GC Root 类型 生命周期 常见泄漏风险
虚拟机栈引用 方法执行期间 无(自动释放)
静态属性引用 类生命周期(通常永久) (静态集合)
常量引用 JVM常量池回收时 中(intern字符串)
JNI Global引用 手动删除 (忘记DeleteGlobalRef)
活跃线程 线程运行期间 中(线程池任务未释放Context)

💡 面试官想要的满分总结

"GC Roots 是可达性分析的起点,主要包括5类:栈帧局部变量、静态变量、常量、JNI引用、活跃线程

特别注意:静态变量引用的对象只要类未卸载就一直是GC Root,容易引发内存泄漏;字符串常量池中的对象在JDK 7+中位于堆上,Full GC时可能被回收。

排查内存泄漏时,用MAT的 Path to GC Roots 功能,找到是哪种Root阻止了对象回收,就能定位问题根源。"


觉得对您有帮助,麻烦 点点关注啦 ,您的关注是我创作的最大动力~ 🎯

相关推荐
笑小枫4 分钟前
用 Claude Code 推翻重写笑小枫网站
java·人工智能·spring boot·ai编程
霸道流氓气质17 分钟前
ApiPost 中配置自动获取 Token 并调用业务接口完整指南
java·服务器·数据库
格林威23 分钟前
工业相机Chunk功能全解析:图像嵌入时间戳、编码器元数据(附堡盟C#代码)
开发语言·人工智能·数码相机·计算机视觉·c#·视觉检测·工业相机
黄敬峰27 分钟前
从零搭建 DeepSeek-R1 WebGPU 聊天应用(一):进度条组件与 React 核心概念
面试
独隅37 分钟前
IntelliJ IDEA 接入多种AI大模型插件终极指南(2026.1 企业合规版)
java·人工智能·intellij-idea
小大宇1 小时前
Python 集成 Conda
开发语言·python·conda
ShineWinsu1 小时前
对于Linux:模版方法类的解析以及socket、TcpSocket的封装
linux·c++·面试·socket·模板方法模式·封装·tcpsocket
不简说1 小时前
JS 代码技巧 vol.8 — 20 个函数式编程实战,把 if/else 拍扁的骚操作
前端·javascript·面试
还是奇怪1 小时前
Simon Willison 用 DSPy 优化 Datasette Agent 提示词:提示工程正在变成可测试的软件工程
java·开发语言·软件工程
zfoo-framework1 小时前
1.ansible安装 2.虚拟机克隆
java