java的finalize()函数

在说明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. 状态变化图:
  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》笔记。 如果有什么不对的地方, 欢迎指正^_^

时间: 2024-11-21 00:14:22

java的finalize()函数的相关文章

Think in java 笔记之 finalize() 函数

1. java垃圾收集机制概述 Java用new关键字生成的对象放在堆空间中,引用句柄则放在栈空间中.一个对象可以有多个引用.由于垃圾收集器的存在,对象是没有作用域的,引用句柄才有作用域.所以,当该对象的最后一个引用的作用域结束,或者该对象的最后一个引用指向了另一个对象或null,也就是说当该对象已经没有被任何引用句柄所引用的时候,该对象就可以被java垃圾收集器所回收了.回收发生的时间是不确定的,不过可以调用gc()方法通知jvm回收垃圾. 2. finalize()方法 它是object类的

java基础知识回顾之java Thread类学习(六)--java多线程同步函数用的锁

1.验证同步函数使用的锁----普通方法使用的锁 思路:创建两个线程,同时操作同一个资源,还是用卖票的例子来验证.创建好两个线程t1,t2,t1线程走同步代码块操作tickets,t2,线程走同步函数封装的代码操作tickets,同步代码块中的锁我们可以指定.假设我们事先不知道同步函数用的是什么锁:如果在同步代码块中指定的某个锁(测试)和同步函数用的锁相同,就不会出现线程安全问题,如果锁不相同,就会发生线程安全问题. 看下面的代码:t1线程用的同步锁是obj,t2线程在操作同步函数的资源,假设不

不使用java内置函数,将String字符串转换为int类型

package com.test; public class AtoiTest { public static void main(String[] args) throws Exception { String s = "-011134"; System.out.println("转换前的字符串:" + s); System.out.println("atoi1转换后的字符串:" + atoi1(s)); System.out.println(

Android使用JNI(从java调用本地函数)

当编写一个混合有本地C代码和Java的应用程序时,需要使用Java本地接口(JNI)作为连接桥梁.JNI作为一个软件层和API,允许使用本地代码调用Java对象的方法,同时也允许在Java方法中调用本地函数. 在Java端,开发者所需要做的仅仅是在连接本地函数的方法之前加上native关键字.这样VM就会去寻找这个本地函数. 1.从Java调用本地函数 从Java调用本地函数时,需要在类中定义一个带有native关键字的特有方法,作为连接本地代码的桥梁.通过这个定义,尝试调用本地方法时JVM会找

c/c++和java实现swap函数的不同处

首先我们来看一下在c/c++中实现的swap函数 void swap ( int & a, int & b) { int Temp; temp = a; a = b; b = temp; } 那么在java中是否还能这样呢,很显然java中没有地址引用符号了. 首先我们来看下c/c++和java的区别. 本质区别 C/C++中swap功能的本质:通过传递变量地址(指针或引用)来交换变量地址中的值. Java标榜其中对C/C++一个很大的改进就是:Java对程序员屏蔽了变量地址的概念,减少指

java String.split()函数的用法分析

在java.lang包中有String.split()方法的原型是:public String[] split(String regex, int limit)split函数是用于使用特定的切割符(regex)来分隔字符串成一个字符串数组,函数返回是一个数组.在其中每个出现regex的位置都要进行分解.需要注意是有以下几点:(1)regex是可选项.字符串或正则表达式对象,它标识了分隔字符串时使用的是一个还是多个字符.如果忽略该选项,返回包含整个字符串的单一元素数组.(2)limit也是可选项.

java中的函数,数组

函数 程序中独立的过程 函数的语法: 函数的定义 声明 static 返回值类型 函数名 (参数表) 形式参数:形参 在函数内部有效的局部变量 实现 { 语句 } 函数的调用 函数名(参数); 实际参数:实参 为形参赋值 接收返回值 函数的作用: 1. 减少冗余代码,提高程序的可维护性 2. 可重用性 函数库 3. 加强代码的结构化 提高可读性 利于分工 非结构化编程(语句) ---> 结构化编程(函数) ---> 面向对象(类) 数组 一次性定义多个同类型的变量 数组空间在内存是连续的 定义

实例介绍 Java(android) 回调函数使用方法

在Android开发中经常用到回调机制,其中最典型的就是控件被触发的实现方式,简单而言,如Button被Click后,是系统调用了OnClick方法,而我们为Button注册了OnClickListener监听器,当被触发Click后,OnClickListener中的OnClick方法就会被回调,我们就能在其中执行相应操作了. 下面举一个简单的例子介绍回调的实现方式: 回调函数使用的简单例子 程序员A写了一段程序(程序a),其中预留有回调函数接口,并封装好了该程序.程序员B要让a调用自己的程序

java静态无名函数(静态初始化块)与java的运行流程

public class Test { public Test() { System.out.println(" 构造函数"); } @1:静态初始化块static { System.out.println("static{}"); } @2:初始化块{ System.out.println("{}"); } public static void main(String[] args) { System.out.println("mai