理解java的finalize

基本预备相关知识

1 java的GC只负责内存相关的清理,所有其它资源的清理必须由程序员手工完成。要不然会引起资源泄露,有可能导致程序崩溃。

2 调用GC并不保证GC实际执行。

3 finalize抛出的未捕获异常只会导致该对象的finalize执行退出。

4 用户可以自己调用对象的finalize方法,但是这种调用是正常的方法调用,和对象的销毁过程无关。

5 JVM保证在一个对象所占用的内存被回收之前,如果它实现了finalize方法,则该方法一定会被调用。Object的默认finalize什么都不做,为了效率,GC可以认为一个什么都不做的finalize不存在。

6 对象的finalize调用链和clone调用链一样,必须手工构造

protected void finalize() throws Throwable {
    super.finalize();
}

对象的销毁过程

在对象的销毁过程中,按照对象的finalize的执行情况,可以分为以下几种,系统会记录对象的对应状态:

unfinalized 没有执行finalize,系统也不准备执行。

finalizable 可以执行finalize了,系统会在随后的某个时间执行finalize。

finalized 该对象的finalize已经被执行了。

GC怎么来保持对finalizable的对象的追踪呢。GC有一个Queue,叫做F-Queue,所有对象在变为finalizable的时候会加入到该Queue,然后等待GC执行它的finalize方法。

这时我们引入了对对象的另外一种记录分类,系统可以检查到一个对象属于哪一种。

reachable 从活动的对象引用链可以到达的对象。包括所有线程当前栈的局部变量,所有的静态变量等等。

finalizer-reachable 除了reachable外,从F-Queue可以通过引用到达的对象。

unreachable 其它的对象。

来看看对象的状态转换图。

1 首先,所有的对象都是从Reachable+Unfinalized走向死亡之路的。

2 当从当前活动集到对象不可达时,对象可以从Reachable状态变到F-Reachable或者Unreachable状态。

3 当对象为非Reachable+Unfinalized时,GC会把它移入F-Queue,状态变为F-Reachable+Finalizable。

4
好了,关键的来了,任何时候,GC都可以从F-Queue中拿到一个Finalizable的对象,标记它为Finalized,然后执行它的
finalize方法,由于该对象在这个线程中又可达了,于是该对象变成Reachable了(并且Finalized)。而finalize方法执行
时,又有可能把其它的F-Reachable的对象变为一个Reachable的,这个叫做对象再生。

5
当一个对象在Unreachable+Unfinalized时,如果该对象使用的是默认的Object的finalize,或者虽然重写了,但是新的实
现什么也不干。为了性能,GC可以把该对象之间变到Reclaimed状态直接销毁,而不用加入到F-Queue等待GC做进一步处理。

6 从状态图看出,不管怎么折腾,任意一个对象的finalize只至多执行一次,一旦对象变为Finalized,就怎么也不会在回到F-Queue去了。当然没有机会再执行finalize了。

7 当对象处于Unreachable+Finalized时,该对象离真正的死亡不远了。GC可以安全的回收该对象的内存了。进入Reclaimed。

对象重生的例子

    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 finalize  
B finalize  
allen is 20

但是有可能失败,源于GC的不确定性以及时序问题,多跑几次应该可以有成功的。详细解释见文末的参考文档。

对象的finalize的执行顺序

所有finalizable的对象的finalize的执行是不确定的,既不确定由哪个线程执行,也不确定执行的顺序。

考虑以下情况就明白为什么了,实例a,b,c是一组相互循环引用的finalizable对象。

何时及如何使用finalize

从以上的分析得出,以下结论。

1 最重要的,尽量不要用finalize,太复杂了,还是让系统照管比较好。可以定义其它的方法来释放非内存资源。

2 如果用,尽量简单。

3 如果用,避免对象再生,这个是自己给自己找麻烦。

4 可以用来保护非内存资源被释放。即使我们定义了其它的方法来释放非内存资源,但是其它人未必会调用该方法来释放。在finalize里面可以检查一下,如果没有释放就释放好了,晚释放总比不释放好。

5 即使对象的finalize已经运行了,不能保证该对象被销毁。要实现一些保证对象彻底被销毁时的动作,只能依赖于java.lang.ref里面的类和GC交互了

时间: 2024-07-31 14:21:05

理解java的finalize的相关文章

《深入理解Java虚拟机 JVM高级特性...》核心笔记

深入理解Java虚拟机 JVM高级特性与最佳实践(第二版) 核心笔记 JAVA 环境: JAVA虚拟机高级特性: 一:java内存区域与内存异常 一):运行数据区     1:程序计数器(Program Counter Register),也称"PC寄存器" A:用来指示需要执行哪条指令的.(在汇编语言中,CPU在得到指令之后,程序计数器便自动加1或者根据                    转移指针得到下一条指令的地址,如此循环,直至执行完所有的指令.) B:由于在JVM中,多线程

《深入理解Java虚拟机》虚拟机性能监控与故障处理工具

上节学习回顾 从课本章节划分,<垃圾收集器>和<内存分配策略>这两篇随笔同属一章节,主要是从理论+实验的手段来讲解JVM的内存处理机制.好让我们对JVM运行机制有一个良好的概念,才能继续往下学习. 本节学习重点 本节主要是针对JVM内存管理机制的一些监控手段,例如堆情况使用的监控,线程栈情况的监控等.有几句废话还是有必要在这里强调的,工具是人类思维的工具,例如Java语言是人类满足需求的一种技术手段,而监控工具只是维护程序应用的一种手段.所以,思考的逻辑思维要清晰,是问题引导工具,

【深入理解Java虚拟机】垃圾回收机制

本文内容来源于<深入理解Java虚拟机>一书,非常推荐大家去看一下这本书. 本系列其他文章: [深入理解Java虚拟机]Java内存区域模型.对象创建过程.常见OOM 1.垃圾回收要解决的问题 垃圾收集(Garbage Collection,GC),要设计一个GC,需要考虑解决下面三件事情: (1)哪些内存需要回收? (2)什么时候回收? (3)如何回收? 哪些内存需要回收? 根据<Java内存区域模型.对象创建过程.常见OOM>中介绍的java内存模型,其中,程序计数器.虚拟机栈

《深入理解Java虚拟机》第二部分(8)

给一个系统定位问题的时候,知识.经验是关键基础,数据是依据,工具是运用知识处理数据的手段.这里的数据包括:运行日志.异常堆栈.GC日志.线程快照(threaddump/javacore文件).堆转储快照(heapdump/hprof文件)等.经常使用适当的虚拟机监控和分析的工具可以加快我们分析数据和定位解决问题的速度,但我们在学习工具前,也应当意识到工具永远都是知识技能的一层包装,没有什么工具是"秘密武器",学会了就能包医百病. JDK的命令行工具 Java开发人员肯定都知道JDK的b

深入理解Java虚拟机之垃圾收集一

"生存还是死亡" 如何来判定对象是否存活?针对这个问题书中给出了两种算法,分别是引用计数算法和可达性分析算法 引用计数算法 该算法的思路简单并且易于实现.我们给对象中添加一个引用计数器,当有一个地方引用它时,引用计数器就加一,当引用失效时,计数器减一,当计数器为0时就说明该对象不可能再被引用. 客观的评价,该算法判定效率很高,在很多情况下都是一种不错的算法,但是,至少主流的Java虚拟机并没有采用采用这种算法.原因是该算法无法解决对象之间的循环引用问题. 什么是循环引用呢?笔者认为就是

jvm--深入理解java虚拟机 精华总结(面试)(转)

深入理解java虚拟机 精华总结(面试)(转) 一.运行时数据区域 3 1.1 程序计数器 3 1.2 Java虚拟机栈 3 1.3 本地方法栈 3 1.4 Java堆 3 1.5 方法区 3 1.6 运行时常量池 4 二. hotspot虚拟机对象 4 2.1 对象的创建 4 检查 4 分配内存 4 Init 4 2.2 对象的内存布局 4 2.3 对象的访问定位 4 使用句柄访问 4 使用直接指针访问 5 三. OutOfMemoryError 异常 5 3.1 Java堆溢出 5 3.2

全面理解Java内存模型(JMM)及volatile关键字(转)

原文地址:全面理解Java内存模型(JMM)及volatile关键字 关联文章: 深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型(@Annotation) 深入理解Java类加载器(ClassLoader) 深入理解Java并发之synchronized实现原理 Java并发编程-无锁CAS与Unsafe类及其并发包Atomic 深入理解Java内存模型(JMM)及volatile关键字 剖析基于并发AQS的重入锁(Reetr

深入理解 Java 线程池

目录   一.简介  二.Executor 框架  三.ThreadPoolExecutor  四.Executors  参考资料 一.简介 什么是线程池 线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务. 为什么要用线程池 如果并发请求数量很多,但每个线程执行的时间很短,就会出现频繁的创建和销毁线程.如此一来,会大大降低系统的效率,可能频繁创建和销毁线程的时间.资源开销要大于实际工作的所需. 正是由于这个问题,所以有必要引入线程池.使用 线程池的好处 有

《深入理解Java集合框架》系列文章

Introduction 关于C++标准模板库(Standard Template Library, STL)的书籍和资料有很多,关于Java集合框架(Java Collections Framework, JCF)的资料却很少,甚至很难找到一本专门介绍它的书籍,这给Java学习者们带来不小的麻烦.我深深的不解其中的原因.虽然JCF设计参考了STL,但其定位不是Java版的STL,而是要实现一个精简紧凑的容器框架,对STL的介绍自然不能替代对JCF的介绍. 本系列文章主要从数据结构和算法层面分析