Java中显式调用System.gc()时finalize()调用机制及相关问题咨询
一、关于《How to Handle Java Finalization's Memory-Retention Issues》的核心内容
这篇文章主要围绕Java finalize机制引发的内存滞留问题展开,核心要点可以拆解成这几点:
- finalize的内存陷阱:当对象重写了
finalize()方法,它被标记为可回收后不会立即销毁,而是会进入Finalizer队列,等待专门的Finalizer线程执行其finalize()方法。这个过程很容易导致对象在内存中“赖着不走”——如果Finalizer线程处理缓慢,或者队列堆积,大量待处理的对象会持续占用内存,甚至引发OOM。更坑的是,如果finalize()里不小心让对象重新被其他引用指向(也就是“对象复活”),那这个对象会再次逃离回收,进一步加剧内存压力。 - 更可靠的替代方案:文章强烈建议尽量抛弃
finalize(),给出了几个更稳妥的选择:- 显式资源释放:比如IO流、数据库连接这类资源,用完后主动调用
close()方法手动释放,这是最直接可控的方式,完全不会有finalize的内存隐患。 - 使用Java 9引入的
CleanerAPI:它通过虚引用(PhantomReference)触发资源清理,既不会导致对象复活,清理线程也是独立的,不会阻塞主线程,内存滞留的风险低很多。
- 显式资源释放:比如IO流、数据库连接这类资源,用完后主动调用
- 非用finalize不可的注意事项:如果因为历史代码或特殊场景必须保留
finalize(),一定要让方法逻辑极简,别做耗时操作,绝对不要在里面让对象重新被引用,尽可能降低内存滞留的概率。
二、为什么显式调用System.gc()时finalize()并非每次都被调用
其实这个问题的核心在于**System.gc()只是给JVM提了个“建议”,而非强制命令**,再加上finalize本身的执行逻辑,就导致了这种不稳定的情况,具体原因有这些:
- JVM的GC自主权:Java规范里明确说了,
System.gc()只是请求JVM做垃圾回收,但JVM完全可以无视这个请求——如果当前内存还足够支撑程序运行,JVM会觉得没必要浪费资源做GC,自然不会执行回收,finalize()也就没机会被调用。不同的GC收集器(比如Serial、G1、ZGC)对这个请求的响应策略也不一样,有些会配合执行,有些则直接忽略。 - finalize的执行是异步且有延迟的:就算JVM真的执行了GC,标记出了需要回收且带有
finalize()的对象,这些对象会被放进Finalizer队列,由低优先级的Finalizer线程去执行方法。如果当前程序有大量高优先级任务在跑,Finalizer线程可能被抢占,导致finalize()不能立即执行,甚至在你观察的时候还没跑完,让你误以为没被调用。 - 对象可能没达到回收条件:如果对象还有其他引用存在,GC根本不会把它标记为可回收对象,
finalize()自然不会被触发。哪怕你调用了System.gc(),这种情况下也没用。 - finalize的“一次性”特性:一个对象的
finalize()方法只会被执行一次——如果第一次执行时对象复活了,之后哪怕它再次变成可回收状态,finalize()也不会再被调用。如果你的测试场景刚好遇到这种情况,也会出现“有时调用有时不调用”的现象。
内容的提问来源于stack exchange,提问作者manoj pandey
相关产品推荐
相关产品推荐

