垃圾如何进行自救

垃圾如何进行自救

背景故事

在Java中,Object是所有类的祖先

img/1.png

Object类中有一个留给子类实现的方法finalize(),它的作用就是在一个对象被当做垃圾进行回收时留给它的最后自救机会

垃圾的发现

JVM的内存管理机制将原本需要程序员自己手动释放的内存可以自动去清理释放,这种方式虽然方便不少,但因为是自动去识别,肯定不如手动释放精准,所以各式各样的垃圾判断方式出现了。

引用计数法

引用计数法的关键在于给每一个实例增加一个被引用次数值,当A中有调用B时,B的引用次数会加一,引用失效时减一,当引用次数为0时,就表示这个实例需要被回收了。

这种算法实现很简单,虽然保存每个对象计数值需要额外的空间,但它还有一个致命问题,就是无法解决循环引用问题。

当两个对象实例互相引用时,该方法就会陷入死循环。

可达性分析法

很多语言的垃圾收集算法都是使用可达性分析。

通过从一个GC Root(通常是方法区中静态变量所引用的对象,虚拟机栈中引用的对象,已经启动且未停止的线程,JNI handles)的对象节点开始,向下搜索它所引用的对象实例,当一个对象可以被搜索到,就说明它是可达的,不需要回收。

当一个对象无法与GC Root连接到一起时,就是需要回收的对象。

在可达性分析的过程中,如果程序还在运行,就有可能会使一个被标记为垃圾的对象重新被引用,所以在这个过程中,会触发STW(Stop the world),应用程序会被停止下来,让垃圾收集器掌握主动权。

Stop The World

STW是根据安全点来实现的, 安全点是确保在垃圾收集前确保线程执行到安全的位置。安全点的选择通常是当前代码段不会发生引用的改变。

生存还是死亡

就算一个对象在可达性分析时被标注为垃圾,但也不是会被立即消除,而是进入一个缓刑状态,等到下一次GC之前,若无人保释,便执行清理工作。

也就是说一个垃圾从标记到回收至少要经历两次GC,第一次是判断释放是垃圾对象,然后垃圾回收器会判断垃圾对象是否有复写finalize()方法,或者说之前是否已经执行过finalize()方法,finalize就是垃圾对象的最后一次自救的机会,符合条件的垃圾对象的finalize()会被加入一个低优先级的线程队列F-Queue,

虽然说垃圾通过finalize来拯救自己听上去是很自强不息的一件事,但是垃圾回收本来就因为STW让我们本来的进程发生停顿,如果垃圾对象里面还有一些复杂的finalize自救,那么一堆对象累计下来,STW的过程就会长到无法想象。

System类中提供了一个runFinalizeion()方法让JVM去积极执行finalize来进行对象销毁,但是这是不可控的,说不定因为finalize就会导致垃圾变慢,还有一堆无法收集的对象,导致我们的程序OOM。

2

这是java.lang.ref中Finalizer的源代码,可以看到其中定义了一个Queue,还有一些add和remove,感兴趣可以自己去看看。

3

在runFinalizer中就是调用垃圾对象finalize()方法的过程了,可以看到catch中,对于执行失败的finalize方法其实是直接忽略的,也就是说就算我们想让这个对象活下去,在特殊异常情况下,这个对象也会死,而且死的不明白的,虚拟机并不会告诉我们任何信息。

所以为了性能着想,最好不要覆写对象的finalize方法,如果对象在回收时有什么操作需要执行,可以使用Cleaner来定制我们自己的处理方式,在独立的线程中处理,而不是在STW中。

posted @ 2019-11-23 17:50  AntzUhl  阅读(279)  评论(0编辑  收藏  举报