java的finalize()函数

时间:2022-09-27 21:24:14

在说明finalize()的用法之前要树立有关于java垃圾回收器几个观点:

  1. "对象可以不被垃圾回收" : java的垃圾回收遵循一个特点, 就是能不回收就不会回收.只要程序的内存没有达到即将用完的地步, 对象占用的空间就不会被释放.因为如果程序正常结束了,而且垃圾回收器没有释放申请的内存, 那么随着程序的正常退出, 申请的内存会自动交还给操作系统; 而且垃圾回收本身就需要付出代价, 是有一定开销的, 如果不使用,就不会存在这一部分的开销.
  2. 垃圾回收只能回收内存, 而且只能回收内存中由java创建对象方式(堆)创建的对象所占用的那一部分内存, 无法回收其他资源, 比如文件操作的句柄, 数据库的连接等等.
  3. 垃圾回收不是C++中的析构. 两者不是对应关系, 因为第一点就指出了垃圾回收的发生是不确定的, 而C++中析构函数是由程序员控制(delete) 或者离开器作用域时自动调用发生, 是在确定的时间对对象进行销毁并释放其所占用的内存.
  4. 调用垃圾回收器(GC)不一定保证垃圾回收器的运行

finalize()的功能 : 一旦垃圾回收器准备释放对象所占的内存空间, 如果对象覆盖了finalize()并且函数体内不能是空的, 就会首先调用对象的finalize(),  然后在下一次垃圾回收动作发生的时候真正收回对象所占的空间.

finalize()有一个特点就是: JVM始终只调用一次. 无论这个对象被垃圾回收器标记为什么状态, finalize()始终只调用一次. 但是程序员在代码中主动调用的不记录在这之内.

如果需要释放对象的父类所占的资源, 那么就必须自己手动构造finalize调用链

protected void finalize() throws Throwable
{
super.finalize();
}  

finalize()主要使用的方面:

  1. 根据垃圾回收器的第2点可知, java垃圾回收器只能回收创建在堆中的java对象, 而对于不是这种方式创建的对象则没有方法处理, 这就需要使用finalize()对这部分对象所占的资源进行释放. 使用到这一点的就是JNI本地对象, 通过JNI来调用本地方法创建的对象只能通过finalize()保证使用之后进行销毁,释放内存
  2. 充当保证使用之后释放资源的最后一道屏障, 比如使用数据库连接之后未断开,并且由于程序员的个人原因忘记了释放连接, 这时就只能依靠finalize()函数来释放资源.
  3. 《thinking in java》中所讲到的“终结条件”验证, 通过finalize()方法来试图找出程序的漏洞

尽管finalize()可以主动调用, 但是最好不要主动调用, 因为在代码中主动调用之后, 如果JVM再次调用, 由于之前的调用已经释放过资源了,所以二次释放资源就有可能出现导致出现空指针等异常, 而恰好这些异常是没有被捕获的, 那么就造成对象处于被破坏的状态, 导致该对象所占用的某一部分资源无法被回收而浪费.

尽量避免使用finalize():

  1. finalize()不一定会被调用, 因为java的垃圾回收器的特性就决定了它不一定会被调用
  2. 就算finalize()函数被调用, 它被调用的时间充满了不确定性, 因为程序中其他线程的优先级远远高于执行finalize()函数线程的优先级。也许等到finalize()被调用, 数据库的连接池或者文件句柄早就耗尽了.
  3. 如果一种未被捕获的异常在使用finalize方法时被抛出,这个异常不会被捕获,finalize方法的终结过程也会终止,造成对象出于破坏的状态。被破坏的对象又很可能导致部分资源无法被回收, 造成浪费.
  4. finalize()和垃圾回收器的运行本身就要耗费资源, 也许会导致程序的暂时停止.

finalize执行的生命周期:

  1. finalize流程:当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收。否则,若对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize方法。执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则,对象“复活”。
  2. 对象可由两种状态组成,涉及到两类状态空间,一是终结状态空间 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:对象不可通过上面两种途径可达, 也就是不可到达, 没有任何对象引用着.
  3. 状态变化图:java的finalize()函数
  4. 具体状态转换:
    1. 新建对象首先处于[reachable, unfinalized]状态(A)
    2. 随着程序的运行,一些引用关系会消失,导致状态变迁,从reachable状态变迁到f-reachable(B, C, D)或unreachable(E, F)状态
    3. 若JVM检测到处于unfinalized状态的对象变成f-reachable或unreachable,JVM会将其标记为finalizable状态(G,H)。若对象原处于[unreachable, unfinalized]状态,则同时将其标记为f-reachable(H)。
    4. 在某个时刻,JVM取出某个finalizable对象,将其标记为finalized并在某个线程中执行其finalize方法。由于是在活动线程中引用了该对象,该对象将变迁到(reachable, finalized)状态(K或J)。该动作将影响某些其他对象从f-reachable状态重新回到reachable状态(L, M, N), 这就是对象重生
    5. 处于finalizable状态的对象不能同时是unreahable的,由第4点可知,将对象finalizable对象标记为finalized时会由某个线程执行该对象的finalize方法,致使其变成reachable。这也是图中只有八个状态点的原因
    6. 程序员手动调用finalize方法并不会影响到上述内部标记的变化,因此JVM只会至多调用finalize一次,即使该对象“复活”也是如此。程序员手动调用多少次不影响JVM的行为
    7. 若JVM检测到finalized状态的对象变成unreachable,回收其内存(I)
    8. 若对象并未覆盖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》笔记。 如果有什么不对的地方, 欢迎指正^_^