【大白话说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 小时前
Java面试85题图解版 · 特别篇:2026后端高频面试题复盘(算法底层逻辑+高并发架构设计全解析,附Java实战代码)
java·网络·数据库·算法·面试
xxie1237944 小时前
return与print
开发语言·python
秋94 小时前
从 Python 后端工程师转型 AI Engineer(AI 工程化)的完整补课清单(2026实战版)
开发语言·人工智能·python
一条泥憨鱼4 小时前
【Redis】数据类型和常用命令
java·数据库·redis·后端·缓存
云烟成雨TD4 小时前
Spring AI Alibaba 1.x 系列【78】沙箱(Sandbox)
java·人工智能·spring
程序员二叉4 小时前
【Java】 异常高频面试题精讲 | 易错点+对比总结
java·开发语言·面试
周航宇JoeZhou5 小时前
JB3-9-SpringAI(二)
java·ai·agent·多智能体·调度·智能体·观察
好家伙VCC5 小时前
Web Components主题热切换方案揭秘
java·前端
慕木沐5 小时前
Google ADK Java 1.0版本 核心机制与实战 Demo
java·开发语言·python
Roann_seo%5 小时前
C++文件操作完全指南:从文本读写到二进制文件处理
开发语言·c++