Java8环境下java.lang.ref.Finalizer对象引发内存泄漏问题排查咨询
问题描述
大多数针对finalizer对象引发内存泄漏的研究表明,无论是自有代码还是依赖库引发泄漏,通常都存在重写finalize方法的情况,但我使用Java8的整个项目中完全没有用到该方法。
我当前遇到了内存问题:服务只要重启,可用内存就会开始下降,7天内可用内存就会减少95%,后续还会频繁触发内存告警。

上图Y轴为可用内存,可以看到内存呈持续下降趋势,图中所有峰值均为服务重启节点。
我通过Eclipse MAT分析堆转储文件时,发现如下情况:
可以看到,几乎所有堆空间都被java.lang.ref.Finalizer这一个对象占用,但我对全项目进行Java检索后,没有发现任何使用finalize()方法的代码。
目前我排查内存泄漏的工作已经陷入瓶颈,请问还有哪些可能的原因会引发该问题?


可能原因及排查方向
- 第三方依赖或JDK内置类的
finalize实现:不需要业务代码主动重写finalize,很多第三方库、甚至JDK内置类本身就实现了该方法。常见的包括旧版JDBC驱动、IO操作类(比如ZipFile)、Socket连接实现、JNI绑定类,这些类的实例会被自动加入Finalizer队列等待处理。 - Finalizer线程被阻塞:JVM会启动一个单独的低优先级Finalizer线程执行所有对象的
finalize方法,如果该线程因为锁等待、IO阻塞、死锁,或者服务CPU长期占满抢不到调度资源,就会导致Finalizer队列持续堆积,所有待回收对象都无法释放内存。你可以通过jstack命令直接查看Finalizer线程的当前栈,确认它卡在哪个类的finalize执行逻辑上。 - JVM参数配置错误:如果你的启动参数加了
-XX:+DisableExplicitGC,会禁用System.gc()调用,而很多包含finalize实现的类会依赖显式GC触发Finalizer队列清理,禁用后会直接导致队列堆积。 - 堆外内存关联的Finalizer泄漏:DirectByteBuffer、JNI分配的native资源大多依赖Finalizer机制回收,如果native资源分配速度远高于Finalizer线程的回收速度,或者JNI逻辑本身存在阻塞,也会导致Finalizer对象大量堆积。
- 类加载器泄漏:如果项目用到动态类加载(比如热部署、动态脚本引擎、动态生成代理类),当Finalizer引用了自定义类加载器加载的对象时,会导致类加载器无法被卸载,连带其加载的所有类、对象都无法回收。
快速排查步骤
- 用MAT打开堆转储,顺着
java.lang.ref.Finalizer的referent属性往下找,确认堆积的Finalizer指向的具体业务类/依赖类实例 - 导出线程栈,查看Finalizer线程的执行状态,定位阻塞点
- 检查JVM启动参数是否包含
-XX:+DisableExplicitGC,如果有先下线该参数验证效果 - 统计堆中所有实现了
finalize方法的类的实例数量,优先排查实例数最高的类来源
内容的提问来源于stack exchange,提问作者arqam
相关产品推荐
相关产品推荐

