java内存泄漏本质是本该不可达的对象仍被gc roots引用而无法回收,常见于静态集合未清理、资源未关闭、监听器未解绑;元空间和直接内存也会泄漏,需用jstat、jmap、mat等工具定位。

Java内存泄漏的本质,不是内存没被回收,而是本该“不可达”的对象,仍被GC Roots牢牢牵着——它活着,但你已经不用它了。
堆中对象为何“死不了”?看清楚GC Roots的四条线
垃圾回收器只清理从GC Roots出发不可达的对象。只要存在一条引用链连到它,哪怕业务逻辑里早把它忘了,它就一直占着堆内存。
常见的GC Roots包括:
Java JDK 25 来自 OpenJDK 官方归档,版本为 JDK 25,本条下载地址已指向官方 Windows x64 zip 安装包直链,适合调试旧项目或兼容旧版 Java 运行环境。
- 正在执行的Java线程的栈帧中的局部变量和参数
- 类的静态字段(static变量是泄漏高发区)
- JNI引用(本地方法持有的Java对象)
- JVM内部的关键对象,如系统类加载器、常量池中指向对象的引用
最典型的三类泄漏场景与代码特征
不是所有引用都合理。以下模式在生产环境高频出现,且极易被忽视:
-
静态集合无清理机制:比如
private static Map<string object> cache = new HashMap();</string>,put进去不remove,也不设过期策略,对象永远存活 -
资源未关闭或未释放:
InputStream、Connection、ByteBuffer.allocateDirect()等,未显式close或clean,底层直接内存/文件句柄持续累积 -
监听器/回调未解绑:注册了事件监听但没提供
removeListener(),或者用ThreadLocal存对象却没调用remove(),导致持有Activity、Servlet或上下文对象无法释放
元空间和直接内存也会泄漏?别只盯着堆
OutOfMemoryError不止出现在堆里。不同区域泄漏表现不同,排查方向也得变:
-
java.lang.OutOfMemoryError: Metaspace → 检查动态生成类(CGLib代理、JSON序列化框架反复创建类型)、大量反射调用、
String.intern()滥用(尤其JDK 7以前) -
OutOfMemoryError: Direct buffer memory → 查
ByteBuffer.allocateDirect()使用点,确认是否漏掉cleaner.clean()或未及时释放 - unable to create new native thread → 线程数爆表,常见于线程池配置不当+ThreadLocal未清理,或递归太深导致栈溢出
定位泄漏:别靠猜,用工具链闭环验证
现象只是入口,证据才决定修复方向:
- 用
jstat -gc <pid></pid>观察老年代持续增长、Full GC频繁但回收量少 → 堆泄漏信号 - 导出堆转储(
jmap -dump:format=b,file=heap.hprof <pid></pid>),用Eclipse MAT分析“Dominator Tree”,找占用大且不应长期存在的对象及其GC Roots - 元空间问题看
jstat -class <pid></pid>中loaded class数量是否只增不减;直接内存看jcmd <pid> VM.native_memory summary</pid>
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










