一、对象何时回收
Java中,使用可达性分析算法标识对象是否回收,即使对象通过可达分析算法被标记为不可达对象,对象不一定被被回收,对象需要经过两次标记才会被回收。在第一次标记后对象会被放入“即将回收”的集合中。对象在随后的判定是否有必要执行finalize()函数后,才会被进行第二次标记,这样,对象才会被回收。
第一次标记:
通过可达性分析算法标记对象不可达,第一次被标记,但是,此时对象并不是一定被回收。处于缓刑阶段。
第二次标记:
对象第一次标记后,随后进行一次筛选,筛选条件是此对象有必要执行finalize()函数。
1. 如果,对象没有覆盖finalize()函数,或者对象的finalize()函数已经被虚拟机调用过,那么,虚拟机将视这两种情况为“finalize()函数没有必要执行”。
2. 如果,对象被判定有必要执行finalize()函数,那么,此对象会被增加到“F-Queue”队列中,F-Queue队列是由虚拟机自动创建的、低优化级的Finalizer线程去执行F-Queue队列中对象的finalize()函数。
随后GC对F-Queue队列的对象进行第二标记小规模的标记。
需要注意,这里的“执行”是指虚拟机开始运行对象的finalize()函数,但并不承诺一定等到finalzie()函数运行结束。这样设计的原因,是对象的finalize()函数如果执行耗时操作或者finalize()函数执行进入死循环,会堵塞Finalizer()线程,会导致虚拟机回收子系统崩溃。
二、通过finalize()函数拯救被标记回收对象
对象回收需要进行两次标记,在第二次标记后才会被回收,第一次标记是通过可达性分析算法完成,随后判定是否有必要执行finalize()函数后进行第二次标记,如果,对象有必要执行finalnze()函数,虚拟机会调用执行对象的finalize()函数。
通过finalize()函数进行自救需要满足的条件:
1. 对象需要覆盖finalize()函数。
2. 对象的finalize()函数没有被虚拟机调用执行过。
示例:
package example; public class GCTest { public static GCTest TEST_HOOK = null; public void isLive() { System.out.println("Test, GC test object is live."); } @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("Test, GC test object is finalize."); TEST_HOOK = this; } } public class Main { public static void main(String[] args) { GCTest.TEST_HOOK = new GCTest(); // 第一次自救 GCTest.TEST_HOOK = null; System.gc(); try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } if (GCTest.TEST_HOOK != null) { GCTest.TEST_HOOK.isLive(); } else { System.out.println("Test, no, GCTest object is dead."); } GCTest.TEST_HOOK = null; System.gc(); if (GCTest.TEST_HOOK != null) { GCTest.TEST_HOOK.isLive(); } else { System.out.println("Test, no, GCTest object is died."); } } }
结果:
Test, GC test object is finalize.
Test, GC test object is live.
Test, no, GCTest object is died.
Process finished with exit code 0
第一次将变量赋值为null时,通知gc回收,会虚拟机会调用执行对象的finalize()函数,在函数中可以尝试自救,在第二次赋值null后,再次通知gc,虚拟机不会再调用对象finalize()函数,对象标记回收。对象的finalize()函数虚拟机只会调用执行一次。