Java通过GC自动回收内存,基于可达性分析判断对象存活,从GC Roots出发搜索引用链,不可达对象被回收;不同GC算法(如Serial、G1、ZGC)权衡吞吐量、延迟与内存占用。
因为Java把内存分配和释放的控制权从开发者手里拿走了——new对象时JVM自动在堆上分配空间,但没有对应的free或delete语法。这避免了C/C++里常见的野指针、重复释放、忘记释放等错误,代价是必须有一套机制来识别哪些对象“已经没人用了”,然后安全地回收它们。这个机制就是垃圾收集器(GC)。
Java GC不用引用计数法(比如Python部分场景用的),而是基于**可达性分析(Reachability Analysis)**:从一组称为GC Roots的引用出发(如栈帧里的局部变量、静态字段、JNI引用等),沿着引用链向下搜索;所有能被遍历到的对象视为“存活”,其余不可达对象就标记为可回收。
这意味着:
Object a = new Object(); Object b = a; a = null; —— 只要b还持有引用,对象就不会被回收WeakReference)、软引用(SoftReference)会影响GC时机,但不改变“是否可达”的基本逻辑
GC不是只有一个实现,而是有多种策略,对应不同应用场景:
Serial GC:单线程,适合小堆、客户端程序;停顿明显,但简单轻量G1 GC(默认自JDK 9起):分区域回收,可预测停顿时间;适合大堆(>4GB)、低延迟要求服务ZGC / Shenandoah:目标是毫秒级停顿(
选错GC可能导致频繁Full GC、STW时间过长、或吞吐量骤降——比如用Serial跑一个8核+32GB堆的Spring Boot服务,基本等于自己给自己加锁。
GC只能回收“不可达”对象,但很多泄漏是对象**本该不可达却一直被意外持有**,比如:
static Map cache )不断put却不removeremove(),导致线程复用时对象长期滞留这类问题不会报错,但堆内存持续增长,最终触发频繁GC甚至OutOfMemoryError: Java heap space。这时候得用jmap + mat或VisualVM分析堆快照,而不是指望GC“更努力一点”。
真正关键的不是GC存在与否,而是理解它不做什么:它不帮你理清业务逻辑里的生命周期,也不替你决定一个对象该活多久。那部分,还是得写代码的人来定。