深入了解Java中finalize方法的作用和底层原理

 更新时间:2022年12月29日 10:58:50   作者:洛神灬殇  
这篇文章主要为大家详细介绍了Java中finalize方法的作用和底层原理,文中的示例代码讲解详细,具有一定的学习价值,需要的可以参考一下

finalize方法是什么

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

finalize方法与C++的析构函数的区别

finalize方法与C++中的析构函数不是对应的,C++中的析构函数调用的时机是确定的(对象离开作用域或delete掉),但Java中的finalize的调用具有不确定性,不建议用finalize方法完成“非内存资源”的清理工作。

finalize方法合适清理的对象

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

可以触发finalize执行的方法

在Java中含有一些一些与finalize相关的方法,由于一些致命的缺陷,已经被废弃了,如System.runFinalizersOnExit() 方法、Runtime.runFinalizersOnExit() 方法、
System.gc() 与System.runFinalization() 方法。

他们增加了finalize方法执行的机会,但不可盲目依赖它们Java语言规范并不保证finalize方法会被及时地执行、而且根本不会保证它们会被执行finalize方法可能会带来性能问题。

因为JVM通常在单独的低优先级线程中完成finalize的执行。

finalize实现对象再生问题

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

finalize方法至多由GC执行一次(用户当然可以手动调用对象的finalize方法,但并不影响GC对finalize的行为)。

finalize的执行过程(生命周期)

大致描述一下finalize的运行流程:当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收。

若对象未执行过finalize方法,将其放入F-Queue队列,由低优先级线程执行该队列中对象的finalize方法。执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则,对象“复活”。

对象对于finalize方法的两种状态

对象可由两种状态,涉及到两类状态空间,一是终结状态空间 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:对象不可通过上面两种途径可达

状态变迁图:

变迁说明:

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)。

在某个时刻,JVM取出某个finalizable对象,将其标记为finalized并在某个线程中执行其finalize方法。

4.由于是在活动线程中引用了该对象,该对象将变迁到(reachable, finalized)状态(K或J)。该动作将影响某些其他对象从f-reachable状态重新回到reachable状态(L, M, N)处于finalizable状态的对象不能同时是unreahable的。

5.将对象finalizable对象标记为finalized时会由某个线程执行该对象的finalize方法,致使其变成reachable。

注:System.runFinalizersOnExit()等方法可以使对象即使处于reachable状态,JVM仍对其执行finalize方法

代码示例

对象复活

public class GC {  
	public static GC SAVE_HOOK = null;    
    public static void main(String[] args) throws InterruptedException {  
        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;  
    }  
}

覆盖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方法并不会影响到上述内部标记的变化,因此JVM只会至多调用finalize一次,即使该对象“复活”也是如此。我们手动调用多少次不影响JVM的行为

若JVM检测到finalized状态的对象变成unreachable,回收其内存(I),若对象并未覆盖finalize方法,JVM会进行优化,直接回收对象(O)

到此这篇关于深入了解Java中finalize方法的作用和底层原理的文章就介绍到这了,更多相关Java finalize方法内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Spring中ApplicationEventPublisher发布订阅模式的实现

    Spring中ApplicationEventPublisher发布订阅模式的实现

    本文主要介绍了Spring中ApplicationEventPublisher发布订阅模式的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-07-07
  • Java实现几种序列化方式总结

    Java实现几种序列化方式总结

    本篇文章主要介绍了Java实现几种序列化方式总结,包括Java原生以流的方法进行的序列化、Json序列化、FastJson序列化、Protobuff序列化,有兴趣的可以了解一下,
    2017-03-03
  • Java实现调用外部程序的示例代码

    Java实现调用外部程序的示例代码

    本文主要介绍了Java实现调用外部程序的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-05-05
  • java打包成可执行的jar或者exe的详细步骤

    java打包成可执行的jar或者exe的详细步骤

    Java程序完成以后,对于Windows操作系统,习惯总是想双击某个exe文件就可以直接运行程序,现我将一步一步的实现该过程.最终结果是:不用安装JRE环境,不用安装数据库,直接双击一个exe文件,就可以运行程序
    2014-04-04
  • 学习SpringBoot容器功能及注解原理

    学习SpringBoot容器功能及注解原理

    这篇文章主要介绍了学习SpringBoot容器功能及注解原理,文中通过详细的代码示例对SpringBoot容器功能及注解原理进行了解析,有需要的朋友可以借鉴参考下
    2021-09-09
  • SpringBoot使用mybatis-plus分页查询无效的问题解决

    SpringBoot使用mybatis-plus分页查询无效的问题解决

    MyBatis-Plus提供了很多便捷的功能,包括分页查询,本文主要介绍了SpringBoot使用mybatis-plus分页查询无效的问题解决,具有一定的参考价值,感兴趣的可以了解一下
    2023-12-12
  • JVM 方法调用之动态分派(详解)

    JVM 方法调用之动态分派(详解)

    下面小编就为大家带来一篇JVM 方法调用之动态分派(详解)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-05-05
  • Java中单例模式的7种写法

    Java中单例模式的7种写法

    这篇文章主要介绍了Java中单例模式的7种写法,本文分别给出每种方式的实现代码,需要的朋友可以参考下
    2015-05-05
  • SpringBoot的WebSocket实现单聊群聊

    SpringBoot的WebSocket实现单聊群聊

    这篇文章主要为大家详细介绍了SpringBoot的WebSocket实现单聊群聊,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-02-02
  • springboot 使用zookeeper实现分布式队列的基本步骤

    springboot 使用zookeeper实现分布式队列的基本步骤

    这篇文章主要介绍了springboot 使用zookeeper实现分布式队列,通过ZooKeeper的协调和同步机制,多个应用程序可以共享一个队列,并按照先进先出的顺序处理队列中的消息,需要的朋友可以参考下
    2023-08-08

最新评论