finalize()的生命周期(执行过程)

说明

本文转载自 Smina俊 的博客:《java finalize方法总结、GC执行finalize的过程》

博文中关于对象复活的示例和生命周期的过程极为精辟,分享给大家。

本文的目的并不是鼓励使用finalize方法,而是大致理清其作用、问题以及GC执行finalize的过程。

finalize的作用

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

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

但建议用于:

① 清理本地对象(通过JNI创建的对象);
② 作为确保某些非内存资源(如Socket、文件等)释放的一个补充:在finalize方法中显式调用其他资源释放方法。

其原因可见下文[finalize的问题]

finalize的问题

一些与finalize相关的方法,由于一些致命的缺陷,已经被废弃了,如System.runFinalizersOnExit()方法、Runtime.runFinalizersOnExit()方法

System.gc()与System.runFinalization()方法增加了finalize方法执行的机会,但不可盲目依赖它们

Java语言规范并不保证finalize方法会被及时地执行、而且根本不会保证它们会被执行

finalize方法可能会带来性能问题。因为JVM通常在单独的低优先级线程中完成finalize的执行

对象再生问题:finalize方法中,可将待回收对象赋值给GC Roots可达的对象引用,从而达到对象再生的目的
finalize方法至多由GC执行一次(用户当然可以手动调用对象的finalize方法,但并不影响GC对finalize的行为)

finalize的执行过程(生命周期)

(1) 首先,大致描述一下finalize流程:

当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收。否则,若对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize方法。执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则,对象“复活”。

(2) 具体的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:对象不可通过上面两种途径可达

img

变迁说明:

  • 新建对象首先处于[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的,由第4点可知,将对象finalizable对象标记为finalized时会由某个线程执行该对象的finalize方法,致使其变成reachable。这也是图中只有八个状态点的原因
  • 程序员手动调用finalize方法并不会影响到上述内部标记的变化,因此JVM只会至多调用finalize一次,即使该对象“复活”也是如此。程序员手动调用多少次不影响JVM的行为
  • 若JVM检测到finalized状态的对象变成unreachable,回收其内存(I)
  • 若对象并未覆盖finalize方法,JVM会进行优化,直接回收对象(O)
  • 注:System.runFinalizersOnExit()等方法可以使对象即使处于reachable状态,JVM仍对其执行finalize方法

对象复活代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public class Demo extends Object{

public static Demo SAVE_HOOK = null;

public static void main(String[] args) throws InterruptedException {

// 新建对象,因为SAVE_HOOK指向这个对象,对象此时的状态是(reachable,unfinalized)
SAVE_HOOK = new Demo();

//将SAVE_HOOK设置成null,此时刚才创建的对象就不可达了,因为没有句柄再指向它了,对象此时状态是(unreachable,unfinalized)
SAVE_HOOK = null;

//强制系统执行垃圾回收,系统发现刚才创建的对象处于unreachable状态,并检测到这个对象的类覆盖了finalize方法,因此把这个对象放入F-Queue队列,
//由低优先级线程执行它的finalize方法,此时对象的状态变成(unreachable, finalizable)或者是(finalizer-reachable,finalizable)
System.gc();

// sleep,目的是给低优先级线程从F-Queue队列取出对象并执行其finalize方法提供机会。在执行完对象的finalize方法中的super.finalize()时,
// 对象的状态变成(unreachable,finalized)状态,但接下来在finalize方法中又执行了SAVE_HOOK = this;这句话,又有句柄指向这个对象了,对象又可达了。
// 因此对象的状态又变成了(reachable, finalized)状态。
Thread.sleep(500);

// 这里对象处于(reachable,finalized)状态。对象的finalized方法被执行了,因此是finalized状态。又因为在finalize方法是执行了SAVE_HOOK=this这句话,
// 本来是unreachable的对象,又变成reachable了。
if (null != SAVE_HOOK) {
//此时对象应该处于(reachable, finalized)状态

// 这句话会输出,注意对象由unreachable,经过finalize复活了。
System.out.println("Yes , I am still alive");
} else {
System.out.println("No , I am dead");
}

// 再一次将SAVE_HOOK放空,此时刚才复活的对象,状态变成(unreachable,finalized)
SAVE_HOOK = null;
// 再一次强制系统回收垃圾,此时系统发现对象不可达,虽然覆盖了finalize方法,但已经执行过了,因此直接回收。
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()");
// 这句话让对象的状态由unreachable变成reachable,就是对象复活
SAVE_HOOK = this;
}
}
---------- 😏本文结束  感谢您的阅读😏 ----------
评论