【大白话说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阻止了对象回收,就能定位问题根源。"


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

相关推荐
William Dawson1 小时前
【通俗易懂!Spring四大核心注解源码解读:@Configuration、@ComponentScan、@Import、@EnableXXX实战】
java·后端·spring
Matlab程序猿小助手1 小时前
【MATLAB源码-第319期】基于matlab的帝王蝶优化算法(MBO)无人机三维路径规划,输出做短路径图和适应度曲线.
开发语言·算法·matlab
Tigshop开源商城1 小时前
Tigshop 开源商城系统 JAVA v5.8.28 版本发布|『角色权限管理+店铺后台跳转逻辑』优化
java·开源商城系统·tigshop
码点滴1 小时前
CRI-O选型与容器运行时标准
开发语言·人工智能·架构·kubernetes·cri-o
回眸&啤酒鸭1 小时前
【回眸】嵌入式软件单元测试工具链实战指南
开发语言·单元测试·白盒测试
彦为君1 小时前
JavaSE-10-并发编程(11个案例)
java·开发语言·python·ai·nio
石山代码1 小时前
java前景
java·开发语言
10岁的博客1 小时前
C++ 进制转换:通用 a 进制转 b 进制(2-36进制)题解
开发语言·c++
图码1 小时前
二分查找进阶:如何在有序数组中快速找到Upper Bound?
数据结构·算法·面试·分类·柔性数组