文章目录
一. 问题背景
遇到一个面试题“GC的两种判定方法(如何判断一个对象是否存活?)”,其中涉及引用计数法和可达性算法,而当一个对象经过可达性分析,会涉及两次标记,其中又涉及finalize()。因此今天了解一下Java对象什么时候死亡以及finalize()方法的执行过程。
此笔记仅供自己参考,如有错误请指正
参考自:JAVA中对象什么时候死亡以及什么时候执行finalize()方法
二. 储备知识
2.1 引用
jdk1.2后,Java将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)
2.1.1 强引用Strong Reference
类似Object obj = new Object();
这类引用,只要引用还在,垃圾收集器就不会回收掉被引用的对象(即new出来的Object)。
2.1.2 软引用Soft Reference
在抛出内存溢出异常之前,会把软引用引用的对象列入回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。
总结:经过一次垃圾回收后,若还是内存不足,则回收掉软引用关联的对象
2.1.3 弱引用Weak Reference
只要进行垃圾回收,就会回收掉引用关联的对象
2.1.4 虚引用Phantom Reference
不会对生存时间构成影响,唯一目的是对象被回收时收到一个系统通知
2.2 可达性算法(引用链法)
从一个被称为GC Root的对象开始向下搜索,一个对象到GC Root没有任何引用链相连时,则说明此对象不可用。
2.3 哪些对象可以作为GC Root
- 虚拟机栈中引用的对象(是对象,不是引用)
- 方法区静态属性引用的对象
- 方法区常量池引用的对象
- 本地方法栈JNI引用的对象
2.4 finalize()相关的知识
2.4.1 finalize()来自哪里
finalize()是Object类里面的方法,而每一个对象都是Object类的子类,因此每一个对象都有finalize()方法(自继承得来的)。
2.4.2 finalize()的作用
用来关闭某些资源(比如数据库连接,IO流等等)。用法是重写finalize()方法即可(在方法体里面进行关闭数据库连接,关闭IO流等等)。
三. 对象的复活(对象什么时候死亡)以及finalize()的执行过程
-
即使对象在可达性算法中被判为不可达,但并非是真正死了,此时它们处于“缓刑阶段”。要真正死亡需要经理2次标记过程。
-
如果对象经过可达性分析后没有与GC Root相连的引用链,那么它将被第一次标记 并 进行一次筛选。
-
筛选的条件是:是否有必要执行finalize()方法。
-
当对象没有覆盖finalize()方法 或 finalize()方法已经被虚拟机调用过,虚拟机将这2中情况视为“没有必要执行”
-
如果对象被判定为有必要执行finalize()方法,那么此对象将被加入到一个叫F-Queue的队列之中,并且稍后由一个虚拟机自行创建的、低优先级的FinalizeHandler线程去执行它。
-
这里的执行是指虚拟机触发这个方法,但不承诺会等待它运行结束。这样做的原因是如果一个对象在finalize()方法中执行缓慢,或者发生死循环,将很可能导致F-Queue队列中其他对象永久处于等待,甚至导致整个内存回收系统崩溃。
-
finalize()方法是对象逃脱死亡的最后一次机会。稍后GC将对F-Queue的对象进行第二次小规模的标记。如果对象要在finalize()方法中拯救自己——只需重新与引用链上的任何一个对象建立关联即可。譬如把自己赋值给类变量或者对象的成员变量,那么在第二次标记时(GC会对F-Queue队列中的对象进行第二次标记)它将被移除出“即将回收”的集合。如果对象此时还没有逃脱,那么基本上它就真的被回收了。
总结:finalize并不是一定要执行,它只能执行1次或0次。如果它能在finalize()中建立对象关联,则当前对象可以复活一次。
过程如下图: