`
zhang_xzhi_xjtu
  • 浏览: 524233 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

深入理解java的finalize

    博客分类:
  • java
阅读更多
目录

基本预备相关知识
对象的销毁过程
对象重生的例子
对象的finalize的执行顺序
何时及如何使用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交互了。

参考

关于引用类型,GC,finalize的相互交互可以参考ReferenceQueue GC finalize Reference 测试及相关问题
  • 大小: 40.5 KB
分享到:
评论
26 楼 随意而生 2015-03-13  
学习学习了
25 楼 urfriend 2012-09-07  
urfriend 写道
qwj528 写道
菜鸟问个问题。。。
A a = new A(new B("allen", 20));
a = null;
System.gc();
                             //这里执行了A的finalize()
                       C.a = this; 那么C.a就等于null
Thread.sleep(5000);
System.out.println(C.a.b); //这里应该出错啊!

菜鸟同问

囧,没注意到
@Override 
    public void finalize() { 
        System.out.println("A finalize"); 
        C.a = this; 
    } 
有个C.a = this;
24 楼 zhang_xzhi_xjtu 2012-09-07  
为什么一定会失败?
对象复生了啊。
23 楼 urfriend 2012-09-07  
qwj528 写道
菜鸟问个问题。。。
A a = new A(new B("allen", 20));
a = null;
System.gc();
                             //这里执行了A的finalize()
                       C.a = this; 那么C.a就等于null
Thread.sleep(5000);
System.out.println(C.a.b); //这里应该出错啊!

菜鸟同问
22 楼 qwj528 2012-06-19  
菜鸟问个问题。。。
A a = new A(new B("allen", 20));
a = null;
System.gc();
                             //这里执行了A的finalize()
                       C.a = this; 那么C.a就等于null
Thread.sleep(5000);
System.out.println(C.a.b); //这里应该出错啊!
21 楼 qwj528 2012-06-19  
A a = new A(new B("allen", 20));
a = null;
System.gc();

Thread.sleep(5000);
System.out.println(C.a.b);

C.a = this;
20 楼 e-beetle 2010-06-28  
hi,哥们,xjtu在杭州啊,//hand

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

对第五点,有点疑问,开始我也认为应该是这样,但“虽然重写了,但是新的实现什么也不干”但我实际用jprofiler试了下,同样会进入queue的。

如果我override了finalize方法,里面什么也没有,还是可以看到有一堆的java.lang.Finalizer。

如果我没这个方法,就看不见java.lang.Finalizer了。能仔细说说这个的理论依据或出处吗?
谢谢
19 楼 zhang_xzhi_xjtu 2009-12-11  
谢谢各位捧场
18 楼 zhao_xiao_dong 2009-12-10  
确实写的很详细了,我明天再看一遍,争取把握住了
17 楼 erlengleng 2009-12-10  
javaeye高手众多啊,此贴我认为不错,但是投新手的多
16 楼 wjm251 2009-10-20  
呵呵,不错,看另一个解释
http://msdn.microsoft.com/en-us/library/aa244021(VS.60).aspx

When an object is first created (A), it is reachable and unfinalized.

As references to an object are discarded during program execution, an object that was reachable may become finalizer-reachable (B, C, D) or unreachable (E, F). (Note that a finalizer-reachable object never becomes unreachable directly; it becomes reachable when the finalizer from which it can be reached is invoked, as explained below.)

If the Java Virtual Machine detects that an unfinalized object has become finalizer-reachable or unreachable, it may label the object finalizable (G, H); moreover, if the object was unreachable, it becomes finalizer-reachable (H).

If the Java Virtual Machine detects that a finalized object has become unreachable, it may reclaim the storage occupied by the object because the object will never again become reachable (I).

At any time, a Java Virtual Machine may take any finalizable object, label it finalized, and then invoke its finalize method in some thread. This causes the object to become finalized and reachable (J, K), and it also may cause other objects that were finalizer-reachable to become reachable again (L, M, N).

A finalizable object cannot also be unreachable; it can be reached because its finalizer may eventually be invoked, whereupon the thread running the finalizer will have access to the object, as this (§15.7.2). Thus, there are actually only eight possible states for an object.

After an object has been finalized, no further action is taken until the automatic storage management determines that it is unreachable. Because of the way that an object progresses from the unfinalized state through the finalizable state to the finalized state, the finalize method is never automatically invoked more than once by a Java Virtual Machine for each object, even if the object is again made reachable after it has been finalized.

Explicit invocation of a finalizer ignores the current state of the object and does not change the state of the object from unfinalized or finalizable to finalized.

If a class does not override method finalize of class Object (or overrides it in only a trivial way, as described above), then if instances of such as class become unreachable, they may be discarded immediately rather than made to await a second determination that they have become unreachable. This strategy is indicated by the dashed arrow (O) in the transition diagram.

Java programmers should also be aware that a finalizer can be automatically invoked, even though it is reachable, during finalization-on-exit (§12.9); moreover, a finalizer can also be invoked explicitly as an ordinary method. Therefore, we recommend that the design of finalize methods be kept simple and that they be programmed defensively, so that they will work in all cases.
15 楼 zhang_xzhi_xjtu 2009-10-19  
patrickyao1988 写道
zhang_xzhi_xjtu 写道

考虑如下场景:
A有一个B的引用。然后A变成垃圾,有待回收。在GC的作用下,A和B被加入F-QUEUE。

这种场景下,B在其他地方应该没有强引用吧?

恩。

zhang_xzhi_xjtu 写道

A和B都是F-Reachable+Finalizable。
当回收A时,A从F-Reachable+Finalizable变到Reachable+Finalized。但是B此时还是在F-QUEUE的,B的状态变成Reachable+Finalizable(因为A的状态为Reachable)。

这样的话,A是经历了F-Reachable+Finalizable,得以从Reachable+Finalizable发展到Reachable+Finalized的吧,那最后那个B
zhang_xzhi_xjtu 写道

以后GC回收B的时候B的状态就从Reachable+Finalizable变为Reachable+Finalized。

是不是也要经历F-Reachable+Finalizable这个状态,直接从这两个状态切换可以吗?
麻烦楼主了!感激不尽


B刚开始是在F-Reachable+Finalizable,然后因为A的关系跳到Reachable+Finalizable,GC对其回收的时候跳到Reachable+Finalized。
14 楼 patrickyao1988 2009-10-16  
zhang_xzhi_xjtu 写道

考虑如下场景:
A有一个B的引用。然后A变成垃圾,有待回收。在GC的作用下,A和B被加入F-QUEUE。

这种场景下,B在其他地方应该没有强引用吧?
zhang_xzhi_xjtu 写道

A和B都是F-Reachable+Finalizable。
当回收A时,A从F-Reachable+Finalizable变到Reachable+Finalized。但是B此时还是在F-QUEUE的,B的状态变成Reachable+Finalizable(因为A的状态为Reachable)。

这样的话,A是经历了F-Reachable+Finalizable,得以从Reachable+Finalizable发展到Reachable+Finalized的吧,那最后那个B
zhang_xzhi_xjtu 写道

以后GC回收B的时候B的状态就从Reachable+Finalizable变为Reachable+Finalized。

是不是也要经历F-Reachable+Finalizable这个状态,直接从这两个状态切换可以吗?
麻烦楼主了!感激不尽
13 楼 zhang_xzhi_xjtu 2009-10-16  
patrickyao1988 写道
楼主我想问下第一行第二第三个图,就是Reachable+Finalizable怎么变到Reachable+Finalized的?


考虑如下场景:
A有一个B的引用。然后A变成垃圾,有待回收。在GC的作用下,A和B被加入F-QUEUE。
A和B都是F-Reachable+Finalizable。
当回收A时,A从F-Reachable+Finalizable变到Reachable+Finalized。但是B此时还是在F-QUEUE的,B的状态变成Reachable+Finalizable(因为A的状态为Reachable)。
以后GC回收B的时候B的状态就从Reachable+Finalizable变为Reachable+Finalized。
12 楼 zhang_xzhi_xjtu 2009-10-16  
patrickyao1988 写道
额  有点混
楼主的意思是被标记为Finalized之后就不会把它再放入F-Queue了吧?
上面说的是"finalizer-reachable 是除了reachable外,从F-Queue可以通过引用到达的对象。"但finalizer-reachable状态并不是指就在F-Queue的,比如finalizer-reachable+Finalized。楼主这样理解可对?

11 楼 patrickyao1988 2009-10-16  
楼主我想问下第一行第二第三个图,就是Reachable+Finalizable怎么变到Reachable+Finalized的?
10 楼 patrickyao1988 2009-10-16  
额  有点混
楼主的意思是被标记为Finalized之后就不会把它再放入F-Queue了吧?
上面说的是"finalizer-reachable 是除了reachable外,从F-Queue可以通过引用到达的对象。"但finalizer-reachable状态并不是指就在F-Queue的,比如finalizer-reachable+Finalized。楼主这样理解可对?
9 楼 zhang_xzhi_xjtu 2009-10-15  


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


看右上角那两个图,上一个指向下一个得箭头就是这种情况:
上一个是Finalized,下一个图F-Queue




下一个不是F queue啊。下一个是F-Reachable+Finalized.
进入F-Queue的状态是F-Reachable+Finalizable。
8 楼 wjm251 2009-10-15  
自己抠一下

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


看右上角那两个图,上一个指向下一个得箭头就是这种情况:
上一个是Finalized,下一个图F-Queue



7 楼 patrickyao1988 2009-10-15  
楼主我又来顶你的文章了。。草草看了第一遍,有些部分看的迷糊,再看一下。。。。

相关推荐

    《剑指offer》Java深入理解final、finally、finalize.pdf

    final 是 Java 中的关键字,它也是 Java 中很重要的一个关键字,final 修饰的类、方法、变量有不同的含义;...本篇文章,cxuan 就带你从这三个关键字入手,带你从用法、应用、原理的角度带你深入浅出理解这三个关键字。

    《深入理解Android》卷Ⅱ

    第2章 深入理解Java Binder和MessageQueue 2.1 概述 2.2 Java层中的Binder架构分析 2.2.1 Binder架构总览 2.2.2 初始化Java层Binder框架 2.2.3 addService实例分析 2.2.4 Java层Binder架构总结 2.3 心系两界...

    Java的Object类讲解案例代码 equals()、hashCode()、finalize()、clone()、wait()

    经验丰富的Java开发者:即使你已经有一定的Java开发经验,仍然值得深入了解Object类的使用。该案例代码将提供一些实际应用场景,帮助你更加灵活地使用Object类的相关方法,从而优化你的项目。 通过这个源码资源,你...

    疯狂JAVA讲义

    6.9.3 finalize方法 228 6.9.4 对象的软、弱和虚引用 230 6.10 修饰符的适用范围 233 6.11 使用JAR文件 234 6.11.1 jar命令详解 235 6.11.2 创建可执行的JAR包 237 6.11.3 关于JAR包的技巧 238 6.12 本章小结...

    java 编程入门思考

    7.2 深入理解 7.2.1 方法调用的绑定 7.2.2 产生正确的行为 7.2.3 扩展性 7.3 覆盖与过载 7.4 抽象类和方法 7.5 接口 7.5.1 Java的“多重继承” 7.5.2 通过继承扩展接口 7.5.3 常数分组 7.5.4 初始化接口中的字段 7.6...

    Java初学者入门教学

    7.2 深入理解 7.2.1 方法调用的绑定 7.2.2 产生正确的行为 7.2.3 扩展性 7.3 覆盖与过载 7.4 抽象类和方法 7.5 接口 7.5.1 Java的“多重继承” 7.5.2 通过继承扩展接口 7.5.3 常数分组 7.5.4 初始化接口中的字段 7.6...

    java联想(中文)

    7.2 深入理解 7.2.1 方法调用的绑定 7.2.2 产生正确的行为 7.2.3 扩展性 7.3 覆盖与过载 7.4 抽象类和方法 7.5 接口 7.5.1 Java的“多重继承” 7.5.2 通过继承扩展接口 7.5.3 常数分组 7.5.4 初始化接口中的字段 7.6...

    JAVA_Thinking in Java

    7.2 深入理解 7.2.1 方法调用的绑定 7.2.2 产生正确的行为 7.2.3 扩展性 7.3 覆盖与过载 7.4 抽象类和方法 7.5 接口 7.5.1 Java的“多重继承” 7.5.2 通过继承扩展接口 7.5.3 常数分组 7.5.4 初始化接口中的字段 7.6...

    Java Object 类高难度进阶版面试题集锦解析Java Object类高难度面试题及答案解析

    提供了20道高难度的Java Object类面试题及详细答案解析,涵盖了equals()、hashCode()、toString()、clone()、finalize()等方法的重写和应用,以及对象...适合准备Java面试的开发者深入理解和掌握Object类的关键知识点。

    Thinking in Java简体中文(全)

    7.2 深入理解 7.2.1 方法调用的绑定 7.2.2 产生正确的行为 7.2.3 扩展性 7.3 覆盖与过载 7.4 抽象类和方法 7.5 接口 7.5.1 Java的“多重继承” 7.5.2 通过继承扩展接口 7.5.3 常数分组 7.5.4 初始化接口中的字段 7.6...

    Thinking in Java 中文第四版+习题答案

    7.2 深入理解 7.2.1 方法调用的绑定 7.2.2 产生正确的行为 7.2.3 扩展性 7.3 覆盖与过载 7.4 抽象类和方法 7.5 接口 7.5.1 Java的“多重继承” 7.5.2 通过继承扩展接口 7.5.3 常数分组 7.5.4 初始化接口中的字段 7.6...

    Thinking in java(中文)

    多形性 7.1 上溯造型 7.1.1 为什么要上溯造型 7.2 深入理解 7.2.1 方法调用的绑定 7.2.2 产生正确的行为 7.2.3 扩展性 7.3 覆盖与过载 7.4 抽象类和方法 7.5 接口 7.5.1 Java的“多重...

    JAVA_Thinking in Java(中文版 由yyc,spirit整理).chm

    7.2 深入理解 7.2.1 方法调用的绑定 7.2.2 产生正确的行为 7.2.3 扩展性 7.3 覆盖与过载 7.4 抽象类和方法 7.5 接口 7.5.1 Java的“多重继承” 7.5.2 通过继承扩展接口 7.5.3 常数分组 7.5.4 初始化接口中的字段 7.6...

    Think in Java(中文版)chm格式

    7.2 深入理解 7.2.1 方法调用的绑定 7.2.2 产生正确的行为 7.2.3 扩展性 7.3 覆盖与过载 7.4 抽象类和方法 7.5 接口 7.5.1 Java的“多重继承” 7.5.2 通过继承扩展接口 7.5.3 常数分组 7.5.4 初始化接口中...

    Thinking in Java(中文版 由yyc,spirit整理).chm

    7.2 深入理解 7.2.1 方法调用的绑定 7.2.2 产生正确的行为 7.2.3 扩展性 7.3 覆盖与过载 7.4 抽象类和方法 7.5 接口 7.5.1 Java的“多重继承” 7.5.2 通过继承扩展接口 7.5.3 常数分组 7.5.4 初始化接口中的字段 7.6...

    安卓java读取网页源码-AndroidLearningNotes:第一次提交

    安卓java读取网页源码 ...java深入源码级的面试题(有难度) 哪些情况下的对象会被垃圾回收机制处理掉? 讲一下常见编码方式? utf-8编码中的中文占几个字节;int型几个字节? 静态代理和动态代理的区别,什么场景使用

    ThinkInJava

    7.2 深入理解 7.2.1 方法调用的绑定 7.2.2 产生正确的行为 7.2.3 扩展性 7.3 覆盖与过载 7.4 抽象类和方法 7.5 接口 7.5.1 Java的“多重继承” 7.5.2 通过继承扩展接口 7.5.3 常数分组 7.5.4 初始化接口中的字段 7.6...

    thinkinjava

    7.2 深入理解 7.2.1 方法调用的绑定 7.2.2 产生正确的行为 7.2.3 扩展性 7.3 覆盖与过载 7.4 抽象类和方法 7.5 接口 7.5.1 Java的“多重继承” 7.5.2 通过继承扩展接口 7.5.3 常数分组 7.5.4 初始化接口中的字段 7.6...

Global site tag (gtag.js) - Google Analytics