Skip to content
阅读进度

Java 的栈、堆与 GC Roots 是什么? ​

返回 性能与稳定性 目录

标准回答 ​

Java 的栈、堆和 GC Roots 分别回答三个问题:方法怎样执行、对象通常放在哪里、哪些对象仍然可达。线程栈保存方法调用帧、局部变量等执行状态;堆主要存放对象和数组;GC Roots 是垃圾回收进行可达性分析的一组起点。

例如方法里写 Object value = new Object(),可以先理解为局部变量 value 保存一个引用,新建对象本体通常在堆里。方法返回使这条局部引用不再有效,但如果其他对象、缓存或回调还持有它,对象仍可能存活。具体运行时还有优化,不能把这个基础模型当作所有优化后的物理内存布局。

GC Roots 常见来源包括活跃线程栈中的引用、JNI 强全局引用以及运行时持有的类等;类的静态字段引用也可能让对象从这些根保持可达。GC 从根沿强引用查找,能到达的对象通常不能作为垃圾回收,已无强可达路径的对象才可能在后续回收阶段释放。Root 可以理解成引用图的出发点,而不是一种你需要继承的 Java 类。

所以内存泄漏常见原因是“业务不用了,但引用链没断”。例如长生命周期单例保存 Activity,这个 Activity 及它引用的 View 仍可能从根到达。排查应找保留它的引用链,而不是只看有没有调用 System.gc;GC 也不会替你注销监听、结束线程或正确关闭所有外部资源。

关键机制 ​

  • 常见根来源包括活跃线程栈引用、已加载类的静态引用,以及 JNI 等持有的引用;具体 GC 实现还有其他根。
  • 强引用保持可达,弱引用不阻止相应回收;软引用行为受内存压力和实现影响,不适合代替可控的缓存策略。
  • Android 运行时还涉及 native、图形缓冲等内存,Java 堆健康不代表整个进程没有内存压力。

通俗例子 ​

局部变量指向一个 Bitmap,方法返回只让这条局部引用消失。如果单例缓存仍持有 Bitmap,它依然不能按“已不用”回收。

追问与回答 ​

两个对象互相引用,会导致 Java GC 回收不了吗? ​

不一定。Java GC 主要依据从根出发的可达性,而不是简单统计引用数量。如果两个对象只互相引用,整个环都不能从 GC Roots 到达,仍可以被回收。真正需要关注的是是否存在从活跃线程、长期容器等根来源到这个环的保留路径。

静态变量为什么容易造成泄漏? ​

静态字段往往依附于生命周期很长的类,在这些类仍然存活时,会让它引用的对象继续保持可达。把 Activity 放进去,就容易让短生命周期页面跟着长期存活。并不是所有 static 都会泄漏,而是字段持有的对象是否超过它应有的生命周期。

易混淆点 ​

GC 不会自动结束线程、关闭连接或解绑回调;资源释放仍需明确的生命周期管理。

参考资料 ​

内容来源 ​

用完整回答理解机制,用真实证据解释项目。