基础
Java技术允许使用finalize()
在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。
这个方法是由垃圾收集器在确定这个对象没有被引用时,对这个对象调用的。它是在Object
类中定义的,因此所有的类都继承了它。子类覆盖finalize()
以整理系统资源或者执行其他清理工作(要不然会引起资源泄露,有可能导致程序崩溃)。finalize()
是在垃圾收集器删除对象之前被自动调用的。
垃圾收集器只知道释放那些由new
分配的内存,所以不知道如何释放对象的“特殊”内存。为解决这个问题,Java提供了一个名为finalize()
,它的工作原理应该是这样的:一旦垃圾收集器准备好释放对象占用的存储空间,它首先调用finalize()
,而且只有在下一次垃圾收集过程中,才会真正回收对象的内存(垃圾回收需要2次)。
所以如果使用finalize()
,就可以在垃圾收集期间进行一些重要的清除或清扫工作(如关闭流等操作)。但JVM(Java虚拟机)不保证此方法总被调用。finalize()
抛出的未捕获异常只会导致该对象的finalize()
执行退出。
用户可以自己调用对象的finalize()
,但是这种调用是正常的方法调用,和对象的销毁过程无关。
对象销毁和对象重生
一个简单的对象生命周期为,Unfinalized
、Finalizable
、Finalized
、Reclaimed
。
在对象的销毁过程中,按照对象的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
其它的对象,不可达对象。
- 首先,所有的对象都是从
Reachable+Unfinalized
(没有执行finalize()
,对象可达)走向死亡之路的。 - 从当前活动集到对象不可达时,对象可以从
Reachable
状态变到F-Reachable
(进入F-Queue
,对象变成finalizable
状态)或者Unreachable
状态。 - 当对象为非
Reachable+Unfinalized
时,GC会把它移入F-Queue
,状态变为F-Reachable+Finalizable
(进入F-Queue
,可达,finalizable
状态)。 - 任何时候,GC都可以从
F-Queue
中拿到一个Finalizable
的对象,标记它为Finalized
,然后执行它的finalize()
,由于该对象在这个线程中又可达了,于是该对象变成Reachable
了(并且Finalized
)。而finalize()
执行时,又有可能把其它的F-Reachable
(进入F-Queue
,finalizable
状态)的对象变为一个Reachable
的,这个叫做对象再生。 - 当一个对象在
Unreachable+Unfinalized
时,如果该对象使用的是默认的Object
的finalize
,或者虽然重写了,但是新的实现什么也不干(子类覆写一个空方法)。为了性能,GC可以把该对象之间变到Reclaimed
状态直接销毁,而不用加入到F-Queue
等待GC做进一步处理。(不可达,不执行finalize()
) - 从状态图看出,不管怎么折腾,任意一个对象的
finalize
只至多执行一次,一旦对象变为Finalized
(执行过finalized()
),就怎么也不会在回到F-Queue
(finalizable
状态)去了。当然没有机会再执行finalize
了。 - 当对象处于
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
54package 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
3A 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 | package demo3; |
1 | package demo3; |
打印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
16package 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 unfinalized
:Finalizer
类实现内部链表。Object lock
:同步监视器对象。
通过断点分析Finalizer
源码来了解Finalizer
创建实例过程。
Finalizer.class
1 | ..... |
VM创建一个覆写finalize()
的JavaBean,会执行register()
,这个方法会创建一个新的Finalizer
类的实例化对象。1
new Finalizer(finalizee);
Finalizer()
,会把覆写finalizer()
的类实例对象添加到ReferenceQueue
(引用队列中),然后执行add()
。1
2super(finalizee, queue);
add();
add()
添加当前覆写finalizer()
的类实例对象(父类的referent
指向覆写finalizer()
的类实例对象)到unfinalized
链表中,这样这个覆写finalize()
的类实例,第1次不会被GC回收,因为有链表引用。1
2
3
4
5
6
7
8
9private 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
31private 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
2Finalizer 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
17private 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()
。