【Java技术专题】「原理专题」深入分析Java中finalize方法的作用和底层原理

简介: 【Java技术专题】「原理专题」深入分析Java中finalize方法的作用和底层原理

finalize方法是什么


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




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


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



finalize方法合适清理的对象


  1. 清理本地对象(通过JNI创建的对象);


  1. 作为确保某些非内存资源(如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的执行过程(生命周期)


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


  1. 若对象未执行过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的执行。

对应的流程图如下所示:

image.png

可达状态空间


各状态含义如下:


  • finalized: 表示GC已经对该对象执行过finalize方法
  • reachable: 表示GC Roots引用可达
  • finalizer-reachable(f-reachable):表示不是reachable,但可通过某个finalizable对象可达
  • unreachable:对象不可通过上面两种途径可达

状态变迁图:

image.png

变迁说明:


  1. 新建对象首先处于[reachable, unfinalized]状态(A)


  1. 随着程序的运行,一些引用关系会消失,导致状态变迁,从reachable状态变迁到f-reachable(B, C, D) 或 unreachable(E, F)状态


  1. JVM检测到处于unfinalized状态的对象变成f-reachable或unreachable。


  • JVM会将其标记为finalizable状态(G,H)。若对象原处于[unreachable, unfinalized]状态,则同时将其标记为f-reachable(H)。



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


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


  1. 将对象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)




相关文章
|
6月前
|
Java
Java语言实现字母大小写转换的方法
Java提供了多种灵活的方法来处理字符串中的字母大小写转换。根据具体需求,可以选择适合的方法来实现。在大多数情况下,使用 String类或 Character类的方法已经足够。但是,在需要更复杂的逻辑或处理非常规字符集时,可以通过字符流或手动遍历字符串来实现更精细的控制。
424 18
|
6月前
|
Java Go 开发工具
【Java】(9)抽象类、接口、内部的运用与作用分析,枚举类型的使用
抽象类必须使用abstract修饰符来修饰,抽象方法也必须使用abstract修饰符来修饰,抽象方法不能有方法体。抽象类不能被实例化,无法使用new关键字来调用抽象类的构造器创建抽象类的实例。抽象类可以包含成员变量、方法(普通方法和抽象方法都可以)、构造器、初始化块、内部类(接 口、枚举)5种成分。抽象类的构造器不能用于创建实例,主要是用于被其子类调用。抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类abstract static不能同时修饰一个方法。
297 0
|
6月前
|
Java 编译器 Go
【Java】(5)方法的概念、方法的调用、方法重载、构造方法的创建
Java方法是语句的集合,它们在一起执行一个功能。方法是解决一类问题的步骤的有序组合方法包含于类或对象中方法在程序中被创建,在其他地方被引用方法的优点使程序变得更简短而清晰。有利于程序维护。可以提高程序开发的效率。提高了代码的重用性。方法的名字的第一个单词应以小写字母作为开头,后面的单词则用大写字母开头写,不使用连接符。例如:addPerson。这种就属于驼峰写法下划线可能出现在 JUnit 测试方法名称中用以分隔名称的逻辑组件。
290 4
|
6月前
|
存储 Java Go
【Java】(3)8种基本数据类型的分析、数据类型转换规则、转义字符的列举
牢记类型转换规则在脑海中将编译和运行两个阶段分开,这是两个不同的阶段,不要弄混!
322 2
|
6月前
|
编解码 Java 开发者
Java String类的关键方法总结
以上总结了Java `String` 类最常见和重要功能性方法。每种操作都对应着日常编程任务,并且理解每种操作如何影响及处理 `Strings` 对于任何使用 Java 的开发者来说都至关重要。
391 5
|
7月前
|
算法 安全 Java
除了类,Java中的接口和方法也可以使用泛型吗?
除了类,Java中的接口和方法也可以使用泛型吗?
227 11
|
7月前
|
Java 开发者
Java 函数式编程全解析:静态方法引用、实例方法引用、特定类型方法引用与构造器引用实战教程
本文介绍Java 8函数式编程中的四种方法引用:静态、实例、特定类型及构造器引用,通过简洁示例演示其用法,帮助开发者提升代码可读性与简洁性。
|
7月前
|
数据采集 存储 弹性计算
高并发Java爬虫的瓶颈分析与动态线程优化方案
高并发Java爬虫的瓶颈分析与动态线程优化方案
|
8月前
|
安全 Java 编译器
new出来的对象,不一定在堆上?聊聊Java虚拟机的优化技术:逃逸分析
逃逸分析是一种静态程序分析技术,用于判断对象的可见性与生命周期。它帮助即时编译器优化内存使用、降低同步开销。根据对象是否逃逸出方法或线程,分析结果分为未逃逸、方法逃逸和线程逃逸三种。基于分析结果,编译器可进行同步锁消除、标量替换和栈上分配等优化,从而提升程序性能。尽管逃逸分析计算复杂度较高,但其在热点代码中的应用为Java虚拟机带来了显著的优化效果。
250 4
|
8月前
|
算法 Java
Java语言实现链表反转的方法
这种反转方法不需要使用额外的存储空间,因此空间复杂度为,它只需要遍历一次链表,所以时间复杂度为,其中为链表的长度。这使得这种反转链表的方法既高效又实用。
614 0
下一篇
开通oss服务