• java的finalize()函数


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

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

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

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

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

    1
    2
    3
    4
    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. 状态变化图:转自http://img.blog.csdn.net/20131006163937765
    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:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    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:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    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》笔记。 如果有什么不对的地方, 欢迎指正^_^

  • 相关阅读:
    CISCO设备的一些硬件知识
    引用 DOS查QQ好友IP(另附入侵方法)
    【转】端口入侵常用工具分析
    修改CentOS默认yum源为国内yum镜像源
    使用Runtime类运行本地可执行文件
    英汉小词典 java随机存取文件流应用 version1.0
    8086/8088汇编过程练习 实现登录界面及其验证
    批处理伪加密
    快速启动中【显示桌面】的还原方法
    批处理字符串偏移指针
  • 原文地址:https://www.cnblogs.com/softwareofjava/p/7290725.html
Copyright © 2020-2023  润新知