【Java 技术专题】「原理专题」深入分析 Java 中 finalize 方法的作用和底层原理
finalize 方法是什么
finalize 方法是 Object 的 protected 方法,Object 的子类们可以覆盖该方法以实现资源清理工作,GC 在首次回收对象之前调用该方法。
finalize 方法与 C++的析构函数的区别
finalize 方法与 C++中的析构函数不是对应的,C++中的析构函数调用的时机是确定的(对象离开作用域或 delete 掉),但 Java 中的 finalize 的调用具有不确定性,不建议用 finalize 方法完成“非内存资源”的清理工作。
finalize 方法合适清理的对象
清理本地对象(通过 JNI 创建的对象);
作为确保某些非内存资源(如 Socket、文件等)释放的一个补充,在 finalize 方法中显式调用其他资源释放方法。
可以触发 finalize 执行的方法
在 Java 中含有一些一些与 finalize 相关的方法,由于一些致命的缺陷,已经被废弃了,如 System.runFinalizersOnExit() 方法、Runtime.runFinalizersOnExit() 方法、System.gc() 与 System.runFinalization() 方法。
他们增加了 finalize 方法执行的机会,但不可盲目依赖它们 Java 语言规范并不保证 finalize 方法会被及时地执行、而且根本不会保证它们会被执行 finalize 方法可能会带来性能问题。
因为 JVM 通常在单独的低优先级线程中完成 finalize 的执行。
finalize 实现对象再生问题
finalize 方法的实现中,可将待回收对象赋值给 GC Roots 可达的对象引用,从而达到对象再生的目的。
finalize 方法至多由 GC 执行一次(用户当然可以手动调用对象的 finalize 方法,但并不影响 GC 对 finalize 的行为)。
finalize 的执行过程(生命周期)
大致描述一下 finalize 的运行流程:当对象变成(GC Roots)不可达时,GC 会判断该对象是否覆盖了 finalize 方法,若未覆盖,则直接将其回收。
若对象未执行过 finalize 方法,将其放入 F-Queue 队列,由低优先级线程执行该队列中对象的 finalize 方法。执行 finalize 方法完毕后,GC 会再次判断该对象是否可达,若不可达,则进行回收,否则,对象“复活”。
对象对于 finalize 方法的两种状态
对象可由两种状态,涉及到两类状态空间,一是终结状态空间 F = {unfinalized, finalizable, finalized};二是可达状态空间 R = {reachable, finalizer-reachable, unreachable}。
终结状态空间
各状态含义如下:
unfinalized: 新建对象会先进入此状态,GC 并未准备执行其 finalize 方法,因为该对象是可达的。
finalizable: 表示 GC 可对该对象执行 finalize 方法,GC 已检测到该对象不可达。正如前面所述,GC 通过 F-Queue 队列和一专用线程完成 finalize 的执行。
对应的流程图如下所示:
可达状态空间
各状态含义如下:
finalized: 表示 GC 已经对该对象执行过 finalize 方法
reachable: 表示 GC Roots 引用可达
finalizer-reachable(f-reachable):表示不是 reachable,但可通过某个 finalizable 对象可达
unreachable:对象不可通过上面两种途径可达
状态变迁图:
变迁说明:
新建对象首先处于[reachable, unfinalized]状态(A)
随着程序的运行,一些引用关系会消失,导致状态变迁,从 reachable 状态变迁到 f-reachable(B, C, D) 或 unreachable(E, F)状态
JVM 检测到处于 unfinalized 状态的对象变成 f-reachable 或 unreachable。
JVM 会将其标记为 finalizable 状态(G,H)。若对象原处于[unreachable, unfinalized]状态,则同时将其标记为 f-reachable(H)。
在某个时刻,JVM 取出某个 finalizable 对象,将其标记为 finalized 并在某个线程中执行其 finalize 方法。
由于是在活动线程中引用了该对象,该对象将变迁到(reachable, finalized)状态(K 或 J)。该动作将影响某些其他对象从 f-reachable 状态重新回到 reachable 状态(L, M, N)处于 finalizable 状态的对象不能同时是 unreahable 的。
将对象 finalizable 对象标记为 finalized 时会由某个线程执行该对象的 finalize 方法,致使其变成 reachable。
注:System.runFinalizersOnExit()等方法可以使对象即使处于 reachable 状态,JVM 仍对其执行 finalize 方法
代码示例
对象复活
覆盖 finalize 方法以确保资源释放
作为一个补充操作,以防用户忘记“关闭“资源,JDK 中 FileInputStream、FileOutputStream、Connection 类均用了此”技术“,下面代码摘自 FileInputStream 类
注意:我们自己手动调用 finalize 方法并不会影响到上述内部标记的变化,因此 JVM 只会至多调用 finalize 一次,即使该对象“复活”也是如此。我们手动调用多少次不影响 JVM 的行为若 JVM 检测到 finalized 状态的对象变成 unreachable,回收其内存(I),若对象并未覆盖 finalize 方法,JVM 会进行优化,直接回收对象(O)
版权声明: 本文为 InfoQ 作者【洛神灬殇】的原创文章。
原文链接:【http://xie.infoq.cn/article/e75ef3d35b3217555fee3cd22】。文章转载请联系作者。
评论