上节提到了标记。下面来说说标记的事情。是不是被标记了就肯定会被回收呢?我们都知道Object类有一个finalize()方法,所有类都继承了Object类,因此也默认实现了这个方法。
finalize方法:
在该对象被回收之前,该对象的finalize()方法会被调用。这里的回收之前指的就是被标记之后,问题就出在这里,有没有一种情况就是原本一个对象开始不在上一章所讲的“关系网”(引用链)中,但是当开发者重写了finalize()后,并且将该对象重新加入到了“关系网”中,也就是说该对象对我们还有用,不应该被回收,但是已经被标记啦,虚拟机的做法是进行两次标记,即第一次标记不在“关系网”中的对象。第二次的话就要先判断该对象有没有实现finalize()方法了,如果没有实现就直接判断该对象可回收;如果实现了就会先放在一个队列中,并由虚拟机建立的一个低优先级的线程去执行它,随后就会进行第二次的小规模标记,在这次被标记的对象就会真正的被回收了。
以上过程可以通过简单的代码示例测试一下finalize方法的简单使用。
通过简单的测试可以发现,同一个对象的finalize的方法只会被调用一次。
详细测试代码如下:
package test02;
/**
* finalize方法的测试
* @Package test02
* @Title: Demo.java
* @Company: $
* @author BurgessLee
* @date 2018年8月20日-下午8:25:00
* @Description: $
*/
public class Demo {
//一个类静态变量
private static Demo demo = null;
public static void main(String[] args) throws InterruptedException {
demo = new Demo();
testHelp();
testHelp();
//我被调用了
//我还活着
//我挂了
}
public static void testHelp() throws InterruptedException{
//从关系网中移除
demo = null;
/**
* 通知要进行垃圾回收
* 但不一定会执行垃圾回收的方法,只是一个通知而已
* 因此在开发的时候不要过多的依赖这个方法,这里只是做一个简单的测试
*/
System.gc();
Thread.sleep(2000);
if(demo == null){
System.out.println("我挂了");
}else{
System.out.println("我还活着");
}
}
@Override
protected void finalize() throws Throwable {
super.finalize();
//重新加入到关系网中
demo = this;
System.out.println("我被调用了");
}
}
如果将代码中的休眠2s的一行注释掉,会发现结果会有所变化。原因是:执行finalize()的是一个低优先级的线程,既然是一个新的线程,虽然优先级低了点,但也是和垃圾收集器并发执行的,所以垃圾收集器没必要等这个低优先级的线程执行完才继续执行。也就是说,finalize()方法不一定会在对象第一次标记后执行。用一句清晰易懂的话来说就是:虚拟机确实有调用方法的动作,但是不会确保在什么时候执行完成。因此也就出现了上面输出的结果,对象被回收之后,那个低优先级的线程才执行完。