基本预备相关知识
1 java的GC只负责内存相关的清理,全部其它资源的清理必须由程序员手工完成。要否则会引发资源泄露,有可能致使程序崩溃。
2 调用GC并不保证GC实际执行。
3 finalize抛出的未捕获异常只会致使该对象的finalize执行退出。
4 用户能够本身调用对象的finalize方法,可是这种调用是正常的方法调用,和对象的销毁过程无关。
5 JVM保证在一个对象所占用的内存被回收以前,若是它实现了finalize方法,则该方法必定会被调用。Object的默认finalize什么都不作,为了效率,GC能够认为一个什么都不作的finalize不存在。
6 对象的finalize调用链和clone调用链同样,必须手工构造。
如
Java代码
- protected void finalize() throws Throwable {
- super.finalize();
- }
对象的销毁过程
在对象的销毁过程当中,按照对象的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走向死亡之路的。
2 当从当前活动集到对象不可达时,对象能够从Reachable状态变到F-Reachable或者Unreachable状态。
3 当对象为非Reachable+Unfinalized时,GC会把它移入F-Queue,状态变为F-Reachable+Finalizable。
4 好了,关键的来了,任什么时候候,GC均可以从F-Queue中拿到一个Finalizable的对象,标记它为Finalized,而后执行它的 finalize方法,因为该对象在这个线程中又可达了,因而该对象变成Reachable了(而且Finalized)。而finalize方法执行 时,又有可能把其它的F-Reachable的对象变为一个Reachable的,这个叫作对象再生。
5 当一个对象在Unreachable+Unfinalized时,若是该对象使用的是默认的Object的finalize,或者虽然重写了,可是新的实 现什么也不干。为了性能,GC能够把该对象之间变到Reclaimed状态直接销毁,而不用加入到F-Queue等待GC作进一步处理。
6 从状态图看出,无论怎么折腾,任意一个对象的finalize只至多执行一次,一旦对象变为Finalized,就怎么也不会在回到F-Queue去了。固然没有机会再执行finalize了。
7 当对象处于Unreachable+Finalized时,该对象离真正的死亡不远了。GC能够安全的回收该对象的内存了。进入Reclaimed。
对象重生的例子
Java代码
- 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 = 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);
- }
- }
期待输出
Java代码
- A finalize
- B finalize
- allen is 20
可是有可能失败,源于GC的不肯定性以及时序问题,多跑几回应该能够有成功的。详细解释见文末的参考文档。
对象的finalize的执行顺序
全部finalizable的对象的finalize的执行是不肯定的,既不肯定由哪一个线程执行,也不肯定执行的顺序。
考虑如下状况就明白为何了,实例a,b,c是一组相互循环引用的finalizable对象。
什么时候及如何使用finalize
从以上的分析得出,如下结论。
1 最重要的,尽可能不要用finalize,太复杂了,仍是让系统照管比较好。能够定义其它的方法来释放非内存资源。
2 若是用,尽可能简单。
3 若是用,避免对象再生,这个是本身给本身找麻烦。
4 能够用来保护非内存资源被释放。即便咱们定义了其它的方法来释放非内存资源,可是其它人未必会调用该方法来释放。在finalize里面能够检查一下,若是没有释放就释放好了,晚释放总比不释放好。
5 即便对象的finalize已经运行了,不能保证该对象被销毁。要实现一些保证对象完全被销毁时的动做,只能依赖于java.lang.ref里面的类和GC交互了。
参考
关于引用类型,GC,finalize的相互交互能够参考
ReferenceQueue GC finalize Reference 测试及相关问题