JVM的finalize()方法
第一次标记不在“链表”中的对象。第二次就要先判断该对象有没有实现finalize()方法,如果没有实现就直接判断该对象可回收;如果实现了就会先放在一个队列中,并由虚拟机建立的一个低优先级的线程去执行它,随后就会进行第二次的小规模标记,在这次被标记的对象就会真正的被回收了。我们来看下面的代码:

结果是:
我被调用啦
我还活着
我挂啦
对同一个对象,它的finalize()方法只会被调用一次,因此第一次调用的时候会进行finalize()方法,并且成功的将该对象加入了“关系网”中,但当第二次回收的时候并不会进入,所以第二次不能将对象加入“关系网”中,导致被回收了。
图中有一行让程序睡眠一秒钟的代码,为的就是确保让低优先级的执行finalize()方法线程执行完成。那如果我们把他注释了会怎样呢?输出结果是:
我挂啦
我被调用啦
我挂啦
不过如果执行很多次的话,也会出现最开始那样的结果,但多数会是这个结果。因为我们已经说,执行finalize()的是一个低优先级的线程,既然是一个新的线程,虽然优先级低,但也是和垃圾收集器并发执行的,所以垃圾收集器没必要等这个低优先级的线程执行完才继续执行。也就是说,finalize()方法不一定会在对象第一次标记后执行。
当虚拟机完成两次标记后,便确认可以回收的对象。但是,垃圾回收并不会阻塞我们程序的线程,与当前程序并发执行的。所以问题就出在这里,当GC线程标记好一个对象的时候,此时我们程序的线程又将该对象重新加入了“关系网”中,当执行二次标记的时候,该对象也没有重写finalize()方法,因此回收的时候就会回收这个不该回收的对象。
虚拟机的解决方法就是在一些特定指令位置设置一些“安全点”,当程序运行到这些“安全点”的时候就会暂停所有当前运行的线程(Stop The World 所以叫STW),暂停后再找到“GC Roots”进行关系的组建,进而执行标记和清除。
这些特定的指令位置主要在:
1、循环的末尾
2、方法临返回前 / 调用方法的call指令后
3、可能抛异常的位置
参考:
https://blog.csdn.net/sunhuaqiang1/article/details/54646578
浙公网安备 33010602011771号