Java对象的三种状态及finalize方法

Java对象的三种状态

可触及态(可达状态):从根节点开始,可以搜索到这个对象,即可以访问到这个对象。

可复活态:从根节点开始,无论如何都不能访问到这个对象,即这个对象的所有引用都被释放,没有任何变量引用该对象了,但是该对象有可能在finalize()方法中再次被引用,从而复活。

不可触及态(不可达状态):对象的所有引用都被释放了,并且在对象的finalize()方法中没有复活。

JVM 在判断对象引用还在不在(是不是可达状态)时是通过一系列GC Roots作为出发点,向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链,即表明从GC Roots到这个对象不可达时,证明此对象不可用,可被回收。如下图所示:对象4、5、6都是可被回收的

GC Roots
对象1
对象2
对象3
对象4
对象5
对象6

可以作为GC Roots的对象:

  • 虚拟机栈中引用的对象

  • 方法区中类静态属性引用的对象

  • 方法区中常量引用的对象

  • 本地方法栈JNI引用的对象

关于finalize

1.finalize()是Object的protected方法,子类可以覆盖该方法以实现资源清理工作,GC在回收对象之前调用该方法。

2.Java中的finalize的调用具有不确定性。Java语言规范并不保证finalize方法会被及时地执行、而且根本不会保证它们会被执行。

3.finalize方法可能会带来性能问题。因为JVM通常在单独的低优先级线程中完成finalize的执行

4.所有对象都有finalize()方法,并且该方法至多由GC执行一次(用户当然可以手动调用对象的finalize方法,但并不影响GC对finalize的行为),并且它的执行时间是随机的,一般在我们为变量赋值为null后会执行,如果我们在某个对象的finalize()方法中为他增加了引用,那么该对象会复活,但是当我们再次为其赋值为null时,finalize()方法不会被执行了,因为该方法知执行一次,该对象也就变成了不可触及态。

5.不建议用finalize方法来释放“非内存资源”资源,原因如下:

  • finalize()方法执行的时间不确定,会导致资源还没来得及释放,又有一个线程需要访问资源,这样就可能会产生异常错误。尤其是在多线程程序中。
  • finalize()方法中可能会发生引用外泄,无意中复活对象,从而产能生内存泄漏。
  • 推荐在try-catch-finally结构的finally块中释放资源。
finalize的作用

1.finalize方法中,可将待回收对象赋值给GC Roots可达的对象引用,从而达到对象再生的目的

2.用于清理以下资源:

  • 清理本地对象(通过JNI创建的对象);
  • 作为确保某些非内存资源(如Socket、文件等)释放的一个补充:在finalize方法中显式调用其他资源释放方法。
finalize的生命周期
大致的流程

1.当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收。

2.若覆盖且对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize方法。

3.执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则,对象“复活”。

具体的流程:

1.对象可有两种状态,涉及到两类状态空间,一是终结状态空间 F = {unfinalized, finalizable, finalized};二是可达状态空间 R = {reachable, finalizer-reachable, unreachable}。各状态含义如下:

  • unfinalized: 新建对象会先进入此状态,GC并未准备执行其finalize方法,因为该对象是可达的
  • finalizable: 表示GC可对该对象执行finalize方法,GC已检测到该对象不可达。正如前面所述,GC通过F-Queue队列和一专用线程完成finalize的执行
  • finalized: 表示GC已经对该对象执行过finalize方法
  • reachable: 表示GC Roots引用可达
  • finalizer-reachable(f-reachable):表示不是reachable,但可通过某个finalizable对象可达
  • unreachable:对象不可通过上面两种途径可达

2.状态变迁图
在这里插入图片描述
3.变迁说明:

  • 新建对象首先处于[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状态的对象不能同时是unreachable的。将对象finalizable对象标记为finalized时会由某个线程执行该对象的finalize方法,致使其变成reachable。这也是图中只有八个状态点的原因
  • 手动调用finalize方法并不会影响到上述内部标记的变化,因此JVM只会至多调用finalize一次,即使该对象“复活”也是如此。程序员手动调用多少次不影响JVM的行为
  • 若JVM检测到finalized状态的对象变成unreachable,回收其内存(I)
  • 若对象并未覆盖finalize方法,JVM会进行优化,直接回收对象(O)
  • System.runFinalizersOnExit()等方法可以使对象即使处于reachable状态,JVM仍对其执行finalize方法
代码

1.对象复活

public class GC { 
   
    public static GC SAVE_HOOK = null; 
   
    public static void main(String[] args) throws InterruptedException {
        // 新建对象,因为SAVE_HOOK指向这个对象,对象此时的状态是(reachable,unfinalized)
        SAVE_HOOK = new GC(); 
        //将SAVE_HOOK设置成null,此时刚才创建的对象就不可达了,因为没有句柄再指向它了,对象此时状态是(unreachable,unfinalized)
        SAVE_HOOK = null; 
        //强制系统执行垃圾回收,系统发现刚才创建的对象处于unreachable状态,并检测到这个对象的类覆盖了finalize方法,因此把这个对象放入F-Queue队列,由低优先级线程执行它的finalize方法,此时对象的状态变成(unreachable, finalizable)或者是(finalizer-reachable,finalizable)
        System.gc(); 
        // sleep,目的是给低优先级线程从F-Queue队列取出对象并执行其finalize方法提供机会。在执行完对象的finalize方法中的super.finalize()时,对象的状态变成(unreachable,finalized)状态,但接下来在finalize方法中又执行了SAVE_HOOK = this;这句话,又有句柄指向这个对象了,对象又可达了。因此对象的状态又变成了(reachable, finalized)状态。
        Thread.sleep(500);
        // 对象的finalized方法被执行了,因此是finalized状态。又因为在finalize方法是执行了SAVE_HOOK=this这句话,本来是unreachable的对象,又变成reachable了。
        if (null != SAVE_HOOK) { //此时对象应该处于(reachable, finalized)状态 
            // 这句话会输出,注意对象由unreachable,经过finalize复活了。
            System.out.println("Yes , I am still alive"); 
        } else { 
            System.out.println("No , I am dead"); 
        } 
        // 再一次将SAVE_HOOK放空,此时刚才复活的对象,状态变成(unreachable,finalized)
        SAVE_HOOK = null; 
        // 再一次强制系统回收垃圾,此时系统发现对象不可达,虽然覆盖了finalize方法,但已经执行过了,因此直接回收。
        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()"); 
       // 这句话让对象的状态由unreachable变成reachable,就是对象复活
        SAVE_HOOK = this; 
    } 
} 

2.覆盖finalize方法以确保资源释放:作为一个补充操作,以防用户忘记“关闭“资源,JDK中FileInputStream、FileOutputStream、Connection类均用了此”技术“,下面代码摘自FileInputStream类

 
    /**
     * Ensures that the <code>close</code> method of this file input stream is
     * called when there are no more references to it.
     *
     * @exception  IOException  if an I/O error occurs.
     * @see        java.io.FileInputStream#close()
     */
    protected void finalize() throws IOException {
        if ((fd != null) &&  (fd != FileDescriptor.in)) {
            /* if fd is shared, the references in FileDescriptor
             * will ensure that finalizer is only called when
             * safe to do so. All references using the fd have
             * become unreachable. We can call close()
             */
            close();
        }
    }

该文finalize部分是转载
作者:高山流水
来源:CSDN
原文:https://blog.csdn.net/rsljdkt/article/details/12242007
版权声明:本文为博主原创文章,转载请附上博文链接!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值