Java GC仅回收不可达对象,内存泄漏等问题仍需开发者干预;判断标准是GC Roots可达性;finalize已废弃,应使用Cleaner或try-with-resources;弱引用GC即回收,软引用内存不足时回收;收集器选择取决于延迟或吞吐量目标。

Java 的 GC 不会“自动解决所有内存问题”,它只负责回收不可达对象,而内存泄漏、频繁 Full GC、堆外内存失控等问题依然需要开发者干预。
GC 什么时候真正开始回收对象
对象是否被回收,唯一判断标准是「是否可达」——即从 GC Roots 出发能否找到该对象的引用链。常见的 GC Roots 包括:正在执行的线程栈帧中的局部变量、静态字段、JNI 引用、JVM 内部对象(如类加载器)等。
注意:finalize() 方法已被废弃(Java 9 起标记为 @Deprecated),不保证执行,也不应依赖它释放资源;Cleaner 或 try-with-resources 才是正确替代方案。
- 对象即使重写了
finalize(),也只可能被最多执行一次,且执行时机不确定 -
System.gc()仅是建议 JVM 运行 GC,不保证触发,生产环境应禁用 - 弱引用(
WeakReference)关联的对象,在下一次 GC 时就会被回收;软引用(SoftReference)则尽量保留到内存不足时
不同垃圾收集器对应用行为的实际影响
选择收集器不是看“最新”或“默认”,而是看延迟敏感度与吞吐量目标。例如:
立即学习“Java免费学习笔记(深入)”;
- 响应时间优先(如 Web API):选
ZGC或Shenandoah,停顿控制在 10ms 内,但需 JDK 11+ 且堆不宜过小(ZGC 推荐 ≥ 8GB) - 吞吐量优先(如批处理):用
Parallel GC(JDK 8 默认),但单次 GC 停顿可能达数百毫秒 - 旧系统兼容性要求高:仍用
ConcMarkSweep GC(CMS)需注意 JDK 9 已移除,JDK 14 彻底删除
可通过启动参数显式指定,例如:-XX:+UseZGC,搭配 -Xmx16g 并非越大越好——ZGC 对大堆更友好,但小堆(
为什么对象没被回收?常见泄漏场景与定位方式
GC 日志本身不直接告诉你“谁持有引用”,但能暴露异常模式:比如老年代持续增长、Full GC 频繁但回收量极少,大概率存在强引用泄漏。
- 静态集合类(
static Map、static List)未清理旧条目是最常见原因 - ThreadLocal 没调用
remove(),尤其在线程池中会导致对象长期滞留 - 监听器/回调注册后未反注册(如 GUI、Netty
ChannelHandler) - 使用
new String(byte[])创建字符串时,底层仍持有一份原始字节数组引用(JDK 7u6 后已修复,但老版本要注意)
实操建议:用 -XX:+PrintGCDetails -Xloggc:gc.log 开启日志,配合 jstat -gc 观察 Eden/Survivor/Old 区变化趋势;怀疑泄漏时用 jmap -histo:live 查看存活对象分布,再用 jstack 或 async-profiler 追踪引用链。
GC 的“自动”二字容易让人误以为可以彻底放手——但堆内存只是 Java 内存的一块,DirectByteBuffer、JNI 分配的堆外内存、甚至文件句柄泄漏,GC 完全无感。这些地方出问题,往往表现为 OOM 但堆 dump 看不出异常。








