深入理解Finalize

基础

Java技术允许使用finalize()在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。
这个方法是由垃圾收集器在确定这个对象没有被引用时,对这个对象调用的。它是在Object类中定义的,因此所有的类都继承了它。子类覆盖finalize()以整理系统资源或者执行其他清理工作(要不然会引起资源泄露,有可能导致程序崩溃)。finalize()是在垃圾收集器删除对象之前被自动调用的。
垃圾收集器只知道释放那些由new分配的内存,所以不知道如何释放对象的“特殊”内存。为解决这个问题,Java提供了一个名为finalize(),它的工作原理应该是这样的:一旦垃圾收集器准备好释放对象占用的存储空间,它首先调用finalize(),而且只有在下一次垃圾收集过程中,才会真正回收对象的内存(垃圾回收需要2次)。
所以如果使用finalize(),就可以在垃圾收集期间进行一些重要的清除或清扫工作(如关闭流等操作)。但JVM(Java虚拟机)不保证此方法总被调用。
finalize()抛出的未捕获异常只会导致该对象的finalize()执行退出。
用户可以自己调用对象的finalize(),但是这种调用是正常的方法调用,和对象的销毁过程无关。

对象销毁和对象重生

一个简单的对象生命周期为,UnfinalizedFinalizableFinalizedReclaimed
在对象的销毁过程中,按照对象的finalize()的执行情况,可以分为以下几种,系统会记录对象的对应状态。

unfinalized

没有执行finalize(),系统也不准备执行。

finalizable

可以执行finalize()了,系统会在随后的某个时间执行finalize

finalized

该对象的finalize()已经被执行了。
GC怎么来保持对finalizable()的对象的追踪呢。GC有一个Queue,叫做F-Queue,所有对象在变为finalizable的时候会加入到该Queue,然后等待GC执行它的finalize()
这时我们引入了对对象的另外一种记录分类,系统可以检查到一个对象属于哪一种:

reachable

从活动的对象引用链可以到达的对象。包括所有线程当前栈的局部变量,所有的静态变量等等。

finalizer-reachable

除了reachable外,从F-Queue可以通过引用到达的对象。

unreachable

其它的对象,不可达对象。

  1. 首先,所有的对象都是从Reachable+Unfinalized(没有执行finalize(),对象可达)走向死亡之路的。
  2. 从当前活动集到对象不可达时,对象可以从Reachable状态变到F-Reachable(进入F-Queue,对象变成finalizable状态)或者Unreachable状态。
  3. 当对象为非Reachable+Unfinalized时,GC会把它移入F-Queue,状态变为F-Reachable+Finalizable(进入F-Queue,可达,finalizable状态)。
  4. 任何时候,GC都可以从F-Queue中拿到一个Finalizable的对象,标记它为Finalized,然后执行它的finalize(),由于该对象在这个线程中又可达了,于是该对象变成Reachable了(并且Finalized)。而finalize()执行时,又有可能把其它的F-Reachable(进入F-Queuefinalizable状态)的对象变为一个Reachable的,这个叫做对象再生。
  5. 当一个对象在Unreachable+Unfinalized时,如果该对象使用的是默认的Objectfinalize,或者虽然重写了,但是新的实现什么也不干(子类覆写一个空方法)。为了性能,GC可以把该对象之间变到Reclaimed状态直接销毁,而不用加入到F-Queue等待GC做进一步处理。(不可达,不执行finalize()
  6. 从状态图看出,不管怎么折腾,任意一个对象的finalize只至多执行一次,一旦对象变为Finalized(执行过finalized()),就怎么也不会在回到F-Queuefinalizable状态)去了。当然没有机会再执行finalize了。
  7. 当对象处于Unreachable+Finalized时,该对象离真正的死亡不远了。GC可以安全的回收该对象的内存了。进入Reclaimed

    对象再生

    例子1

    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
    package demo1;


    class C {
    static A a;
    }

    class A {
    B b;

    public A(B b) {
    this.b = b;
    }

    @Override
    public void finalize() {
    System.out.println("A finalize");
    //C实例化a引用
    C.a = this;
    }
    }

    class B {
    String name;
    int age;

    public B(String name, int age) {
    this.name = name;
    this.age = age;
    }

    @Override
    public void finalize() {
    System.out.println("B finalize");
    }

    @Override
    public String toString() {
    return name + " is " + age;
    }
    }

    public class Main {
    public static void main(String[] args) throws Exception {
    A a = new A(new B("allen", 20));
    //强制销毁
    a = null;

    System.gc();
    Thread.sleep(5000);
    //销毁之后,可能会重生
    System.out.println(C.a.b);
    }
    }

控制台

1
2
3
A finalize
B finalize
allen is 20

A类中持有B类的引用,A对象=null,强制销毁,执行finalize(),这个方法里,有重新赋值。这个对象从finalizer-reachable状态 -> reachable-finalized,这个对象又重新创建了。
但是,对象只能复活一次;在垃圾回收过程中,不能对复活对象调用finalize(),因为复活的对象第一次finalize运行过后,该对象的finalizable置为false了(状态已经变成finalized),该对象即使以后被GC运行,也不会执行finalize()了,所以对象只能复活1次。

例子2

1
2
3
4
5
6
7
8
9
10
11
package demo3;

public class B {

static B b;

public void finalize() {
System.out.println("method B.finalize");
b = this;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package demo3;

public class M {

public static void main(String[] args) {
B b = new B();
b = null;
//第1次GC,b再生
System.gc();
//赋值为空,再次GC,b被销毁,但是已经被标注为执行过finalize
B.b = null;
System.gc();
}

}

打印

1
method B.finalize

Finalize执行顺序

所有finalizable状态的对象的finalize()的执行是不确定的,既不确定由哪个线程执行,也不确定执行的顺序。考虑以下情况就明白为什么了,实例a,b,c是一组相互循环引用的finalizable对象。
finalize()操作具有下列限制:垃圾回收过程中执行准确时间是不确定的。不保证资源在任何特定的时间都能释放,除非调用Close()Dispose()。即使一个对象引用另一个对象,也不能保证两个对象的finalize()以任何特定的顺序运行。即,如果对象A具有对对象B的引用,并且两者都有finalize(),则当对象A的finalize()启动时,对象B可能已经运行结束了。

何时及如何使用finalize

  • 最重要的,尽量不要用finalize,太复杂了,还是让系统照管比较好。可以定义其它的方法来释放非内存资源。
  • 如果用,尽量简单。
  • 如果用,避免对象再生,这个是自己给自己找麻烦。
  • 可以用来保护非内存资源被释放。即使定义了其它的方法来释放非内存资源,但是其它人未必会调用该方法来释放。在finalize里面可以检查一下,如果没有释放就释放好了,晚释放总比不释放好。
  • 即使对象的finalize已经运行了,不能保证该对象被销毁。要实现一些保证对象彻底被销毁时的动作,只能依赖于java.lang.ref里面的类和GC交互了。

    finalize的安全性

    一个类覆写finalize(),通过jmap命令查看,创建很多finalize实例对象。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    package demo3;

    public class P {

    public static void main(String[] args) {
    while (true) {
    P heap = new P();
    System.out.println("memory address=" + heap);
    }
    }

    @Override
    public void finalize(){
    System.out.println("finalize.");
    }
    }

创建Finalizer类的实例有12923个。

Finalizer类属性结构

  • ReferenceQueue queue:引用队列。
  • Finalizer unfinalizedFinalizer类实现内部链表。
  • Object lock:同步监视器对象。

通过断点分析Finalizer源码来了解Finalizer创建实例过程。

Finalizer.class

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
.....
final class Finalizer extends FinalReference { /* Package-private; must be in
same package as the Reference
class */

private static ReferenceQueue queue = new ReferenceQueue();
private static Finalizer unfinalized = null;
private static final Object lock = new Object();

private Finalizer
next = null,
prev = null;

private boolean hasBeenFinalized() {
return (next == this);
}

private void add() {
synchronized (lock) {
if (unfinalized != null) {
this.next = unfinalized;
unfinalized.prev = this;
}
unfinalized = this;
}
}

private void remove() {
synchronized (lock) {
if (unfinalized == this) {
if (this.next != null) {
unfinalized = this.next;
} else {
unfinalized = this.prev;
}
}
if (this.next != null) {
this.next.prev = this.prev;
}
if (this.prev != null) {
this.prev.next = this.next;
}
this.next = this; /* Indicates that this has been finalized */
this.prev = this;
}
}

private Finalizer(Object finalizee) {
super(finalizee, queue);
add();
}

/* Invoked by VM */
static void register(Object finalizee) {
new Finalizer(finalizee);
}

private void runFinalizer(JavaLangAccess jla) {
synchronized (this) {
if (hasBeenFinalized()) return;
remove();
}
try {
Object finalizee = this.get();
if (finalizee != null && !(finalizee instanceof java.lang.Enum)) {
jla.invokeFinalize(finalizee);

/* Clear stack slot containing this variable, to decrease
the chances of false retention with a conservative GC */
finalizee = null;
}
} catch (Throwable x) { }
super.clear();
}

/* Create a privileged secondary finalizer thread in the system thread
group for the given Runnable, and wait for it to complete.

This method is used by both runFinalization and runFinalizersOnExit.
The former method invokes all pending finalizers, while the latter
invokes all uninvoked finalizers if on-exit finalization has been
enabled.

These two methods could have been implemented by offloading their work
to the regular finalizer thread and waiting for that thread to finish.
The advantage of creating a fresh thread, however, is that it insulates
invokers of these methods from a stalled or deadlocked finalizer thread.
*/
private static void forkSecondaryFinalizer(final Runnable proc) {
AccessController.doPrivileged(
new PrivilegedAction<Void>() {
public Void run() {
ThreadGroup tg = Thread.currentThread().getThreadGroup();
for (ThreadGroup tgn = tg;
tgn != null;
tg = tgn, tgn = tg.getParent());
Thread sft = new Thread(tg, proc, "Secondary finalizer");
sft.start();
try {
sft.join();
} catch (InterruptedException x) {
/* Ignore */
}
return null;
}});
}

/* Called by Runtime.runFinalization() */
static void runFinalization() {
if (!VM.isBooted()) {
return;
}

forkSecondaryFinalizer(new Runnable() {
private volatile boolean running;
public void run() {
if (running)
return;
final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
running = true;
for (;;) {
Finalizer f = (Finalizer)queue.poll();
if (f == null) break;
f.runFinalizer(jla);
}
}
});
}

/* Invoked by java.lang.Shutdown */
static void runAllFinalizers() {
if (!VM.isBooted()) {
return;
}

forkSecondaryFinalizer(new Runnable() {
private volatile boolean running;
public void run() {
if (running)
return;
final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
running = true;
for (;;) {
Finalizer f;
synchronized (lock) {
f = unfinalized;
if (f == null) break;
unfinalized = f.next;
}
f.runFinalizer(jla);
}}});
}

private static class FinalizerThread extends Thread {
private volatile boolean running;
FinalizerThread(ThreadGroup g) {
super(g, "Finalizer");
}
public void run() {
if (running)
return;

// Finalizer thread starts before System.initializeSystemClass
// is called. Wait until JavaLangAccess is available
while (!VM.isBooted()) {
// delay until VM completes initialization
try {
VM.awaitBooted();
} catch (InterruptedException x) {
// ignore and continue
}
}
final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
running = true;
for (;;) {
try {
Finalizer f = (Finalizer)queue.remove();
f.runFinalizer(jla);
} catch (InterruptedException x) {
// ignore and continue
}
}
}
}

static {
ThreadGroup tg = Thread.currentThread().getThreadGroup();
for (ThreadGroup tgn = tg;
tgn != null;
tg = tgn, tgn = tg.getParent());
Thread finalizer = new FinalizerThread(tg);
finalizer.setPriority(Thread.MAX_PRIORITY - 2);
finalizer.setDaemon(true);
finalizer.start();
}

}

VM创建一个覆写finalize()的JavaBean,会执行register(),这个方法会创建一个新的Finalizer类的实例化对象。

1
new Finalizer(finalizee);

Finalizer(),会把覆写finalizer()的类实例对象添加到ReferenceQueue(引用队列中),然后执行add()

1
2
super(finalizee, queue);
add();

add()添加当前覆写finalizer()的类实例对象(父类的referent指向覆写finalizer()的类实例对象)到unfinalized链表中,这样这个覆写finalize()的类实例,第1次不会被GC回收,因为有链表引用。

1
2
3
4
5
6
7
8
9
private void add() {
synchronized (lock) {
if (unfinalized != null) {
this.next = unfinalized;
unfinalized.prev = this;
}
unfinalized = this;
}
}

FinalizerThread是JVM内部的守护线程,优先级很低,是个单一职责的线程。

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
private static class FinalizerThread extends Thread {
private volatile boolean running;
FinalizerThread(ThreadGroup g) {
super(g, "Finalizer");
}
public void run() {
if (running)
return;

// Finalizer thread starts before System.initializeSystemClass
// is called. Wait until JavaLangAccess is available
while (!VM.isBooted()) {
// delay until VM completes initialization
try {
VM.awaitBooted();
} catch (InterruptedException x) {
// ignore and continue
}
}
final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
running = true;
for (;;) {
try {
Finalizer f = (Finalizer)queue.remove();
f.runFinalizer(jla);
} catch (InterruptedException x) {
// ignore and continue
}
}
}
}

该线程从引用队列中删除并获取finalizer对象,finalizer对象执行runFinalizer()

1
2
Finalizer f = (Finalizer)queue.remove();
f.runFinalizer(jla);

runFinalizer()是删除finalizer链表中的finalizer对象,并且执行该对象的finalize(),删除finalizer链表中的对象,这样Finalizer的链表就没有指向覆写finalizer()的类实例(父类的referent指向覆写finalizer()的类实例对象),则该实例就会在下一次GC被回收。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void runFinalizer(JavaLangAccess jla) {
synchronized (this) {
if (hasBeenFinalized()) return;
remove();
}
try {
Object finalizee = this.get();
if (finalizee != null && !(finalizee instanceof java.lang.Enum)) {
jla.invokeFinalize(finalizee);

/* Clear stack slot containing this variable, to decrease
the chances of false retention with a conservative GC */
finalizee = null;
}
} catch (Throwable x) { }
super.clear();
}

总结

  • 如果一个类A实现了finalize(),那么每次创建A类对象的时候,都会多创建一个Finalizer对象(Finalizer父类的referent指向覆写finalizer()的类实例对象);如果类没有实现finalize(),那么不会创建额外的Finalizer对象。
  • Finalizer内部维护了一个unfinalized链表,每次创建的Finalizer对象都会插入到该链表中。
  • 如果类没有实现finalize(),那么进行垃圾回收的时候,可以直接从堆内存中释放该对象。这是速度最快,效率最高的方式。
  • 如果类实现了finalize(),进行GC的时候,如果发现某个对象只被Finalizer对象引用(父类的referent属性引用),那么会将该Finalizer对象加入到Finalizer类的引用队列(ReferenceQueue)中,并添加到unfinalized链表中。
  • runFinalizer是JVM内部的守护线程,优先级很低。Finalizer线程是个单一职责的线程。这个线程会不停的循环等待java.lang.ref.Finalizer.ReferenceQueue中的新增对象。一旦Finalizer线程发现队列中出现了新的对象,它会弹出该对象,将该引用从Finalizer类链表中移除,调用它的finalize(),因此下次GC再执行的时候,这个Finalizer实例以及它引用的那个对象(覆写finalizer()的类实例对象)就可以回垃圾回收掉了。
  • 使用finalize()容易导致OOM,因为如果创建对象的速度很快,那么Finalizer线程的回收速度赶不上创建速度,就会导致内存垃圾越来越多,这些对象从Eden移动到Survivor区,如果拷贝溢出,就将溢出的数据晋升到Old(老年代),进行Full GC

注意:覆写finalize(),不能够证明GC执行了2次。实际上是1个线程执行:添加对象到Finalizer链表中;另一个线程执行从链表中删除这个对象,执行finalizer()

# Java

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×