如果类中重写了finalize
方法,当该类对象被回收时,finalize
方法有可能会被触发,下面通过一个例子说明finalize
方法对垃圾回收有什么影响。
public class FinalizeCase {
private static Block holder = null;
public static void main(String[] args) throws Exception {
holder = new Block();
holder = null;
System.gc();
//System.in.read();
}
static class Block {
byte[] _200M = new byte[200*1024*1024];
}
}
Block
类中声明一个占用内存200M的数组,是为了方便看出来gc之后是否回收了Block
对象,执行完的gc日志如下:
从gc日志中可以看出来,执行完System.gc()
之后,Block
对象被如期的回收了,如果在Block
类中重写了finalize
方法,会是一样的结果么?
static class Block {
byte[] _200M = new byte[200*1024*1024];
@Override
protected void finalize() throws Throwable {
System.out.println("invoke finalize");
}
}
执行完成gc日志如下:
和之前的gc日志进行比较,发现finalize
方法确实被触发了,但是Block
对象还在内存中,并没有被回收,这是为什么?
下面对finalize
方法的实现原理进行分析。
《JVM源码分析之Java对象的创建过程》一文中分析了Java对象创建的整个过程,代码实现如下:
对象的初始化过程会对has_finalizer_flag
和RegisterFinalizersAtInit
进行判断,如果类重写了finalize
方法,且方法体不为空,则调用register_finalizer
函数,继续看register_finalizer
函数的实现:
其中Universe::finalizer_register_method()
缓存的是jdk
中java.lang.ref.Finalizer
类的register
方法,实现如下:
在jvm中通过JavaCalls::call
触发register
方法,将新建的对象O
封装成一个Finalizer
对象,并通过add
方法添加到Finalizer
链表头。
对象O
和Finalizer
类的静态变量unfinalized
有联系,在发生GC时,会被判定为活跃对象,因此不会被回收
在Finalizer
类的静态代码块中会创建一个FinalizerThread
类型的守护线程,但是这个线程的优先级比较低,意味着在cpu吃紧的时候可能会抢占不到资源执行。
FinalizerThread
线程负责从ReferenceQueue
队列中获取Finalizer
对象,如果队列中没有元素,则通过wait
方法将该线程挂起,等待被唤醒
如果返回了Finalizer
对象,执行对象的runFinalizer()
方法,其实可以发现:在runFinalizer()
方法中主动捕获了异常,即使在执行finalize
方法抛出异常时,也没有关系。
通过hasBeenFinalized
方法判断该对象是否还在链表中,并将该Finalizer
对象从链表中删除,这样下次gc时就可以把原对象给回收掉了,最后调用了native方法invokeFinalizeMethod
,其中invokeFinalizeMethod
方法最终会找到并执行对象的finalize
方法。
有个疑问:既然FinalizerThread
线程是从ReferenceQueue
队列中获取Finalizer
对象,那么Finalizer
对象是在什么情况下才会被插入到ReferenceQueue
队列中?
Finalizer
的祖父类Reference
中定义了ReferenceHandler
线程,实现如下:
当pending
被设置时,会调用ReferenceQueue
的enqueue
方法把Finalizer
对象插入到ReferenceQueue
队列中,接着通过notifyAll
方法唤醒FinalizerThread
线程执行后续逻辑,实现如下:
pending字段什么时候会被设置?
在GC过程的引用处理阶段,通过oopDesc::atomic_exchange_oop
方法把发现的引用列表设置在pending
字段所在的地址
平常使用的Socket通信,SocksSocketImpl
的父类重写了finalize
方法
这么做主要是为了确保在用户忘记手动关闭socket
连接的情况下,在该对象被回收时能够自动关闭socket
来释放一些资源,但是在开发过程中,真的忘记手动调用了close
方法,那么这些socket
对象可能会因为FinalizeThread
线程迟迟没有执行到这些对象的finalize
方法,而导致一直占用某些资源,造成内存泄露。
资料来源:http://www.jianshu.com/p/9d2788fffd5f
补充:
final
修饰符(关键字)如果一个类被声明为final,意味着它不能再派生出新的子类,不能作为父类被继承。因此一个类不能既被声明为 abstract的,又被声明为final的。将变量或方法声明为final,可以保证它们在使用中不被改变。被声明为final的变量必须在声明时给定初值,而在以后的引用中只能读取,不可修改。被声明为final的方法也同样只能使用,不能重载。
finally
异常处理时提供 finally 块来执行任何清除操作。如果抛出一个异常,那么相匹配的 catch 子句就会执行,然后控制就会进入 finally 块(如果有的话)。一般异常处理块需要。
finalize
方法名。Java 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在 Object 类中定义的,因此所有的类都继承了它。子类覆盖 finalize() 方法以整理系统资源或者执行其他清理工作。finalize() 方法是在垃圾收集器删除对象之前对这个对象调用的。
Java中所有类都从Object类中继承finalize()方法。
当垃圾回收器(garbage colector)决定回收某对象时,就会运行该对象的finalize()方法。值得C++程序员注意的是,finalize()方法并不能等同与析构函数。Java中是没有析构函数的。C++的析构函数是在对象消亡时运行的。由于C++没有垃圾回收,对象空间手动回收,所以一旦对象用不到时,程序员就应当把它delete()掉。所以析构函数中经常做一些文件保存之类的收尾工作。但是在Java中很不幸,如果内存总是充足的,那么垃圾回收可能永远不会进行,也就是说filalize()可能永远不被执行,显然指望它做收尾工作是靠不住的。
那么finalize()究竟是做什么的呢?它最主要的用途是回收特殊渠道申请的内存。Java程序有垃圾回收器,所以一般情况下内存问题不用程序员操心。但有一种JNI(Java Native Interface)调用non-Java程序(C或C++),finalize()的工作就是回收这部分的内存。