写点什么

【Java 技术专题】「原理专题」深入分析 Java 中 finalize 方法的作用和底层原理

作者:洛神灬殇
  • 2022-12-29
    江苏
  • 本文字数:2539 字

    阅读完需:约 8 分钟

【Java技术专题】「原理专题」深入分析Java中finalize方法的作用和底层原理

finalize 方法是什么

finalize 方法是 Object 的 protected 方法,Object 的子类们可以覆盖该方法以实现资源清理工作,GC 在首次回收对象之前调用该方法。

finalize 方法与 C++的析构函数的区别

finalize 方法与 C++中的析构函数不是对应的,C++中的析构函数调用的时机是确定的(对象离开作用域或 delete 掉),但 Java 中的 finalize 的调用具有不确定性,不建议用 finalize 方法完成“非内存资源”的清理工作。

finalize 方法合适清理的对象

  1. 清理本地对象(通过 JNI 创建的对象);

  2. 作为确保某些非内存资源(如 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 的执行过程(生命周期)

  1. 大致描述一下 finalize 的运行流程:当对象变成(GC Roots)不可达时,GC 会判断该对象是否覆盖了 finalize 方法,若未覆盖,则直接将其回收。

  2. 若对象未执行过 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:对象不可通过上面两种途径可达


状态变迁图:



变迁说明:


  1. 新建对象首先处于[reachable, unfinalized]状态(A)

  2. 随着程序的运行,一些引用关系会消失,导致状态变迁,从 reachable 状态变迁到 f-reachable(B, C, D) 或 unreachable(E, F)状态

  3. JVM 检测到处于 unfinalized 状态的对象变成 f-reachable 或 unreachable。

  4. JVM 会将其标记为 finalizable 状态(G,H)。若对象原处于[unreachable, unfinalized]状态,则同时将其标记为 f-reachable(H)。


在某个时刻,JVM 取出某个 finalizable 对象,将其标记为 finalized 并在某个线程中执行其 finalize 方法。


  1. 由于是在活动线程中引用了该对象,该对象将变迁到(reachable, finalized)状态(K 或 J)。该动作将影响某些其他对象从 f-reachable 状态重新回到 reachable 状态(L, M, N)处于 finalizable 状态的对象不能同时是 unreahable 的。

  2. 将对象 finalizable 对象标记为 finalized 时会由某个线程执行该对象的 finalize 方法,致使其变成 reachable。


注:System.runFinalizersOnExit()等方法可以使对象即使处于 reachable 状态,JVM 仍对其执行 finalize 方法

代码示例

对象复活

public class GC {    public static GC SAVE_HOOK = null;        public static void main(String[] args) throws InterruptedException {          SAVE_HOOK = new GC();          SAVE_HOOK = null;          System.gc();          Thread.sleep(500);          if (null != SAVE_HOOK) { //此时对象应该处于(reachable, finalized)状态              System.out.println("Yes , I am still alive");          } else {              System.out.println("No , I am dead");          }          SAVE_HOOK = null;          System.gc();          Thread.sleep(500);          if (null != SAVE_HOOK) {              System.out.println("Yes , I am still alive");          } else {              System.out.println("No , I am dead");          }      }        @Override      protected void finalize() throws Throwable {          super.finalize();          System.out.println("execute method finalize()");          SAVE_HOOK = this;      }  }  
复制代码

覆盖 finalize 方法以确保资源释放

作为一个补充操作,以防用户忘记“关闭“资源,JDK 中 FileInputStream、FileOutputStream、Connection 类均用了此”技术“,下面代码摘自 FileInputStream 类


/**  * Ensures that the <code>close</code> method of this file input stream is  * called when there are no more references to it.  *  * @exception  IOException  if an I/O error occurs.  * @see        java.io.FileInputStream#close()  */  protected void finalize() throws IOException {      if ((fd != null) &&  (fd != FileDescriptor.in)) {          /* if fd is shared, the references in FileDescriptor          * will ensure that finalizer is only called when          * safe to do so. All references using the fd have          * become unreachable. We can call close()          */        close();    }}
复制代码


注意:我们自己手动调用 finalize 方法并不会影响到上述内部标记的变化,因此 JVM 只会至多调用 finalize 一次,即使该对象“复活”也是如此。我们手动调用多少次不影响 JVM 的行为若 JVM 检测到 finalized 状态的对象变成 unreachable,回收其内存(I),若对象并未覆盖 finalize 方法,JVM 会进行优化,直接回收对象(O)

发布于: 刚刚阅读数: 5
用户头像

洛神灬殇

关注

🏆 InfoQ写作平台-签约作者 🏆 2020-03-25 加入

【个人简介】酷爱计算机科学、醉心编程技术、喜爱健身运动、热衷悬疑推理的“极客达人” 【技术格言】任何足够先进的技术都与魔法无异 【技术范畴】Java领域、Spring生态、MySQL专项、微服务/分布式体系和算法设计等

评论

发布
暂无评论
【Java技术专题】「原理专题」深入分析Java中finalize方法的作用和底层原理_Java_洛神灬殇_InfoQ写作社区