finalize方法
本文是Reference相关内容的最后一篇了。在这一篇,我们介绍一下 FinalReference 和 finalize方法。
finalize方法定义在Object中:
protected void finalize() throws Throwable { }
在对象中我们可以重定义这个方法。在这个方法中可以释放各种资源。关于这一点,相信大多数人都比较熟悉了。但是关于 finalize 到底什么时候被调用,好像没有人能说清楚。我们来看一下 hotspot 到底是怎么来实现的。
首先,hotspot 里会把类的构造方法的最后一条指令,也就是Java虚拟机指令的return,重写为一个特殊的指令:
void Rewriter::rewrite_Object_init(methodHandle method, TRAPS) {
RawBytecodeStream bcs(method);
while (!bcs.is_last_bytecode()) {
Bytecodes::Code opcode = bcs.raw_next();
switch (opcode) {
case Bytecodes::_return: *bcs.bcp() = Bytecodes::_return_register_finalizer; break;
// 以下代码略
这个指令,在解释器里是这样被执行的:
if (_desc->bytecode() == Bytecodes::_return_register_finalizer) {
assert(state == vtos, "only valid state");
__ movptr(c_rarg1, aaddress(0));
__ load_klass(rdi, c_rarg1);
__ movl(rdi, Address(rdi, Klass::access_flags_offset()));
__ testl(rdi, JVM_ACC_HAS_FINALIZER);
Label skip_register_finalizer;
__ jcc(Assembler::zero, skip_register_finalizer);
__ call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::register_finalizer), c_rarg1);
__ bind(skip_register_finalizer);
}
这其实是一段手写的汇编,大概是这样的,Klass结构的指针先加载到rdi寄存器,然后,去取得它的acess_flag,去检查这个Klass是否定义了finalize方法。如果一个类里定义了finalize,那么在加载的时候,就会使它的access_flag中的JVM_ACC_HAS_FINALIZER置位。
也就是说,如果这个Klass中定义了finalize方法,就会调用 register_finalizer 这个方法。这个方法我们就不去看了,有兴趣的同学自己去查。
反正最终呢,会调用到Finalizer.java中定义的这个类中:
/* Invoked by VM */
static void register(Object finalizee) {
new Finalizer(finalizee);
}
就是说,如果一个定义了finalize方法的类在初始化的时候,就会调用一下这个static方法,生成一个Finalizer对象,而我们自己的对象就是这个方法中所使用的finalizee。
也就是说,我们新建一个带 finalize 方法的对象,就会伴生一个 Finalizer 对象。我们看一下,这个类的定义:
final class Finalizer extends FinalReference<Object> { /* Package-private; must be in
same package as the Reference
class */
private static ReferenceQueue<Object> 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();
}
Hoho,绕了半天,这和上节课所讲的Cleaner何其相似,都是一个双向队列,在构造的时候就把新建的Finalizer对象加入到一个双向链表中。
注意上面这段代码里的queue,大家还记得Reference Handler线程吗?那个线程会把所有的Reference从pending链表上取出来,然后加入到一个queue中。对于FinalReference,就都会加入到上述代码中的queue中。这个queue中的所有对象,都会被一个名为Finalizer的线程所处理。我们之前也介绍过Reference Handler是2号线程,Finalizer线程是3号线程。Finalizer线程的代码如下所示:
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对象,然后去调用它的runFinalizer方法。
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();
}
这个方法使用了JavaLangAccess做为参数,其实就是一次虚函数调用,调用到finalizee中的 finalize 方法。
hasBeenFinalized的作用就是保证finalize方法只会被调用一次。
Finalizer vs. Cleaner
因为Finalizer也是一种Reference,所以前边Reference的处理逻辑是和Weak, Soft reference的逻辑十分相似的。
而且Finalizer和Cleaner的作用也十分相似,但有一个巨大的不同在于,finalize方法里可以使object 复活,而 Cleaner 的 clean 方法中不能使得对象复活。
这是因为 finalize 中,可以通过 this 指针访问到 object 对象,例如:
public void finalize() {
Other.ref = this;
}
这样的话,一个本来应该被回收的对象又在finalize之后复活了。但是Cleaner为什么不行呢?因为它的基类是一个PhantomReference,这个“鬼引用”的 get 方法是这样的:
public class PhantomReference<T> extends Reference<T> {
public T get() {
return null;
}
// 其它代码略
}
永远返回null,也就是说对于Cleaner,创建了以后,就再也不能访问它的referent了。
所以现在有人提案,在Java中去掉finalize方法,只使用Cleaner来维护。
其实,这里面还有其他的一些细节,比如为什么Weak的referent会被JVM清掉,而Finalizer又不会清掉自己的referent呢。这些就都不重要了。我就不再讲解了。
我想,经过了这四节课,我们就可以把WeakReference, SoftReference, PhantomReference和Cleaner, FinalReference和Finalizer这6种对象全部搞清楚了。
上一节课:PhantomReference & Cleaner
下一节课:Tracing GC(1)
课程目录:课程目录