java-深入理解java GC与 finalize()

来源:互联网 发布:网站数据维护 编辑:程序博客网 时间:2024/05/17 06:37

基本预备相关知识

1 java的GC只负责内存相关的清理,所有其它资源的清理必须由程序员手工完成。要不然会引起资源泄露,有可能导致程序崩溃。

2 调用GC并不保证GC实际执行。

   GC不保证资源在任何特定的时间都能释放,除非调用 Close 方法或Dispose 方法。

3 finalize抛出的未捕获异常只会导致该对象的finalize执行退出。

  protected void finalize() throws Throwable

4 引用对象

     垃圾收集器每次运行时都可以随意地释放不再是强可及的对象占用的内存。如果垃圾收集器发现了软可及对象,就会出现下列情况:
  (1)SoftReference 对象的 referent 域被设置为 null ,从而使该对象不再引用 heap 对象。
  (2)SoftReference 引用过的 heap 对象被声明为 finalizable 。
  (3)当 heap 对象的 finalize() 方法被运行而且该对象占用的内存被释放, SoftReference 对象就被添加到它的 ReferenceQueue (如果后者存在的话)。
  如果垃圾收集器发现了弱可及对象,就会出现下列情况:
  (1)WeakReference 对象的 referent 域被设置为 null ,从而使该对象不再引用 heap 对象。
  (2)WeakReference 引用过的 heap 对象被声明为 finalizable 。
  (3)当 heap 对象的 finalize() 方法被运行而且该对象占用的内存被释放时, WeakReference 对象就被添加到它的 ReferenceQueue (如果后者存在的话)。
  如果垃圾收集器发现了虚可及对象,就会出现下列情况:
  (1)PhantomReference 引用过的 heap 对象被声明为 finalizable 。
       (2)与软引用和弱引用有所不同, PhantomReference 在堆对象被释放之前就被添加到它的 ReferenceQueue 。(请记住,所有的 PhantomReference 对象都必须用经过关      联的 ReferenceQueue 来创建。)这使您能够在堆对象被回收之前采取行动。

4 用户可以自己调用对象的finalize方法,但是这种调用是正常的方法调用,和对象的销毁过程无关。

5 JVM保证在一个对象所占用的内存被回收之前,如果它实现了finalize方法,则该方法一定会被调用。Object的默认finalize什么都不做,为了效率,GC可以认为一个什么都不做的finalize不存在。

6 对象的finalize调用链和clone调用链一样,必须手工构造。

Java代码 复制代码收藏代码
  1. protectedvoid finalize()throws Throwable { 
  2.     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代码 复制代码收藏代码
  1. class C { 
  2.     static A a; 
  3.  
  4. class A { 
  5.     B b; 
  6.  
  7.     public A(B b) { 
  8.         this.b = b; 
  9.     } 
  10.  
  11.     @Override 
  12.     publicvoid finalize() { 
  13.         System.out.println("A finalize"); 
  14.         C.a = this
  15.     } 
  16.  
  17. class B { 
  18.     String name; 
  19.     int age; 
  20.  
  21.     public B(String name,int age) { 
  22.         this.name = name; 
  23.         this.age = age; 
  24.     } 
  25.  
  26.     @Override 
  27.     publicvoid finalize() { 
  28.         System.out.println("B finalize"); 
  29.     } 
  30.  
  31.     @Override 
  32.     public String toString() { 
  33.         return name +" is " + age; 
  34.     } 
  35.  
  36. public class Main { 
  37.     publicstaticvoid main(String[] args)throws Exception { 
  38.         A a = new A(new B("allen",20)); 
  39.         a = null
  40.  
  41.         System.gc(); 
  42.         Thread.sleep(5000); 
  43.         System.out.println(C.a.b); 
  44.     } 


期待输出
Java代码 复制代码收藏代码
  1. A finalize 
  2. B finalize 
  3. allen is 20 

但是有可能失败,源于GC的不确定性以及时序问题,多跑几次应该可以有成功的。详细解释见文末的参考文档。

对象的finalize的执行顺序

所有finalizable的对象的finalize的执行是不确定的,既不确定由哪个线程执行,也不确定执行的顺序。
考虑以下情况就明白为什么了,实例a,b,c是一组相互循环引用的finalizable对象。

何时及如何使用finalize

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