在说明finalize()的用法之前要树立有关于java垃圾回收器几个观点:
- "对象可以不被垃圾回收" : java的垃圾回收遵循一个特点, 就是能不回收就不会回收.只要程序的内存没有达到即将用完的地步, 对象占用的空间就不会被释放.因为如果程序正常结束了,而且垃圾回收器没有释放申请的内存, 那么随着程序的正常退出, 申请的内存会自动交还给操作系统; 而且垃圾回收本身就需要付出代价, 是有一定开销的, 如果不使用,就不会存在这一部分的开销.
- 垃圾回收只能回收内存, 而且只能回收内存中由java创建对象方式(堆)创建的对象所占用的那一部分内存, 无法回收其他资源, 比如文件操作的句柄, 数据库的连接等等.
- 垃圾回收不是C++中的析构. 两者不是对应关系, 因为第一点就指出了垃圾回收的发生是不确定的, 而C++中析构函数是由程序员控制(delete) 或者离开器作用域时发生, 是在确定的时间对对象进行销毁并释放其所占用的内存.
- 调用垃圾回收器(GC)不一定保证垃圾回收器的运行
finalize()的功能 : 一旦垃圾回收器准备释放对象所占的内存空间, 如果对象覆盖了finalize()并且函数体内不能是空的, 就会首先调用对象的finalize(), 然后在下一次垃圾回收动作发生的时候真正收回对象所占的空间.
finalize()有一个特点就是: JVM始终只调用一次. 无论这个对象被垃圾回收器标记为什么状态, finalize()始终只调用一次. 但是程序员在代码中主动调用的不记录在这之内.
如果需要释放对象的父类所占的资源, 那么就必须自己手动构造finalize调用链
protected void finalize() throws Throwable { super.finalize(); }
finalize()主要使用的方面:
- 根据垃圾回收器的第2点可知, java垃圾回收器只能回收创建在堆中的java对象, 而对于不是这种方式创建的对象则没有方法处理, 这就需要使用finalize()对这部分对象所占的资源进行释放. 使用到这一点的就是JNI本地对象, 通过JNI来调用本地方法创建的对象只能通过finalize()保证使用之后进行销毁,释放内存
- 充当保证使用之后释放资源的最后一道屏障, 比如使用数据库连接之后未断开,并且由于程序员的个人原因忘记了释放连接, 这时就只能依靠finalize()函数来释放资源.
- 《thinking in java》中所讲到的“终结条件”验证, 通过finalize()方法来试图找出程序的漏洞
尽管finalize()可以主动调用, 但是最好不要主动调用, 因为在代码中主动调用之后, 如果JVM再次调用, 由于之前的调用已经释放过资源了,所以二次释放资源就有可能出现导致出现空指针等异常, 而恰好这些异常是没有被捕获的, 那么就造成对象处于被破坏的状态, 导致该对象所占用的某一部分资源无法被回收而浪费.
尽量避免使用finalize():
- finalize()不一定会被调用, 因为java的垃圾回收器的特性就决定了它不一定会被调用
- 就算finalize()函数被调用, 它被调用的时间充满了不确定性, 因为程序中其他线程的优先级远远高于执行finalize()函数线程的优先级。也许等到finalize()被调用, 数据库的连接池或者文件句柄早就耗尽了.
- 如果一种未被捕获的异常在使用finalize方法时被抛出,这个异常不会被捕获,finalize方法的终结过程也会终止,造成对象出于破坏的状态。被破坏的对象又很可能导致部分资源无法被回收, 造成浪费.
- finalize()和垃圾回收器的运行本身就要耗费资源, 也许会导致程序的暂时停止.
finalize执行的生命周期:
- finalize流程:当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收。否则,若对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize方法。执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则,对象“复活”。
- 对象可由两种状态组成,涉及到两类状态空间,一是终结状态空间 F = {unfinalized, finalizable, finalized};二是可达状态空间 R = {reachable, finalizer-reachable, unreachable}。
- unfinalized : GC未调用对象的finalize(), 也不准备调用对象的finalize().
- finalizable: 表示GC可调用对象的finalize(),但是还未调用
- finalized: 表示GC已经调用过了该对象的finalize()
- reachable: 表示GC Roots引用可达, 比如在栈中有变量引用该对象
- finalizer-reachable(f-reachable):表示不是reachable,但可通过某个finalizable对象可达
- unreachable:对象不可通过上面两种途径可达, 也就是不可到达, 没有任何对象引用着.
- 状态变化图:
- 具体状态转换:
- 新建对象首先处于[reachable, unfinalized]状态(A)
- 随着程序的运行,一些引用关系会消失,导致状态变迁,从reachable状态变迁到f-reachable(B, C, D)或unreachable(E, F)状态
- 若JVM检测到处于unfinalized状态的对象变成f-reachable或unreachable,JVM会将其标记为finalizable状态(G,H)。若对象原处于[unreachable, unfinalized]状态,则同时将其标记为f-reachable(H)。
- 在某个时刻,JVM取出某个finalizable对象,将其标记为finalized并在某个线程中执行其finalize方法。由于是在活动线程中引用了该对象,该对象将变迁到(reachable, finalized)状态(K或J)。该动作将影响某些其他对象从f-reachable状态重新回到reachable状态(L, M, N), 这就是对象重生
- 处于finalizable状态的对象不能同时是unreahable的,由第4点可知,将对象finalizable对象标记为finalized时会由某个线程执行该对象的finalize方法,致使其变成reachable。这也是图中只有八个状态点的原因
- 程序员手动调用finalize方法并不会影响到上述内部标记的变化,因此JVM只会至多调用finalize一次,即使该对象“复活”也是如此。程序员手动调用多少次不影响JVM的行为
- 若JVM检测到finalized状态的对象变成unreachable,回收其内存(I)
- 若对象并未覆盖finalize方法,JVM会进行优化,直接回收对象(O)
注:System.runFinalizersOnExit()等方法可以使对象即使处于reachable状态,JVM仍对其执行finalize方法
对象重生的代码1:
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); } }
我的理解:为方便起见, 把a,b两个变量所指的内存空间就叫做a和b
- A a = new A(new B("allen" , 20)) ; //此时a和b都是reachable, unfinalized状态
- a = null ;
- 这之后, a和b的状态会在某一个时刻变成unreachable, unfinalized(但是b变成了unreachable还是f-reachable我不是很确定, 如果大家知道,欢迎补充^_^)
- 或者a和b直接变成f-reachable, unfianlized.
- 然后在某个时刻,GC检测到a和b处于unfinalized状态, 就将他们添加到F-queue,并将状态改为f-reachable finalizable.
- 之后分两种情况:
- 第一: GC从F-queue中首先取出a, 并被某个线程执行了finalize(), 也就相当于被某个活动的线程持有, a状态变成了reachable, finalized. 此时由于a被c对象所引用,所以之后不会变成unreachable finalized而被销毁(重生) 与此同时, b由于一直被a所引用, 所以b的状态变成了reachable, finalizable. 然后在某个时刻被从F-queue取出, 变成reachable, finalized状态
- 第二: GC从F-queue中首先取出b,并被某个线程执行了finalize(), 状态变成reachable finalized. 然后a也类似, 变成reachable finalized状态, 并被c引用, 重生
对象重生的代码2:
public class GC { public static GC SAVE_HOOK = null; public static void main(String[] args) throws InterruptedException, Throwable { SAVE_HOOK = new GC(); SAVE_HOOK = null; System.gc(); Thread.sleep(500); if (null != SAVE_HOOK) //此时对象应该处于(reachable, finalized)状态 { System.out.println("Yes , I am still alive"); } else { System.out.println("No , I am dead"); } SAVE_HOOK = null; System.gc(); Thread.sleep(500); if (null != SAVE_HOOK) { System.out.println("Yes , I am still alive"); } else { System.out.println("No , I am dead"); } } @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("execute method finalize()"); SAVE_HOOK = this; } }
//资料来自: http://blog.csdn.net/rsljdkt/article/details/12242007# ; http://blog.sina.com.cn/s/blog_66a6172c01018jda.html ; http://zhang-xzhi-xjtu.iteye.com/blog/484934
//《thingking in java》笔记。 如果有什么不对的地方, 欢迎指正^_^