Java虚拟机核心知识(四) 如何判断对象是否需要回收

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u013667756/article/details/82943679

前言

前面两篇文章,给大家讲解了Java的内存区域以及内存溢出和内存泄漏的区别,对于程序计数器虚拟机栈本地方法栈这三个区域的数据,它们的生命周期可以说是伴随着整个线程周期,每个栈帧分配多少内存,也基本是在类结构确定的时候就已知了(即编译期),因此这几个区域的内存分配和回收,都具有确定性,我们不需要考虑太多的回收问题。

我们常说的垃圾回收,主要指的是Java堆方法区的垃圾回收。那为什么我们关注的垃圾回收在这一部分呢?

其实是由于编译期只知道对象的静态类型,一个方法中需要创建多少对象,也只有在运行期才知道,因此,这些部分的内存分配和回收都是动态的,垃圾收集器关注的是这部分的内存。

对象回收的判断策略

引用计数算法

这种算法,给每个对象设置一个引用计数器,每当有一个地方引用它时,计数器加1;引用失效时,计数器减1;计数器为0,意味着对象独自在堆中,上,不可能再被使用,这时就可以回收了。
在这里插入图片描述
这种算法,实现简单,效率也很高,但是有一个致命的缺陷——很难解决对象之间相互引用的问题。

可达性分析算法

这个算法的基本思路是:通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,当GC Roots到一个对象不可达时,则证明这个对象是不可用的,可以将其回收。
在这里插入图片描述
在Java中,可作为GC Roots的对象主要有两种:

(1) 全局性的对象,如常量或者类的静态属性,如果一个对象被全局对象所引用,那就不能被回收。

(2) 执行上下文,如栈帧中的局部变量,如果方法上下文中有局部变量引用了这个对象,那就不能被回收。

总体来说,这些对象可以是:

虚拟机栈(栈帧中的本地变量表)中引用的对象。

方法区中的类静态属性引用的对象

方法区中的常量引用的对象

本地方法栈JNI中的引用的对象。

可达就一定不会被回收?

默认情况下,到GC Roots可达的对象都不会被回收,这种对象,我们成为“强引用”。

Java对引用的概念进行了扩充,将引用分为强引用(StrongReference)、软引用(SoftReference)、弱引用(WeakReference)和虚引用(PhantomReference)4种:

强引用:也就是默认的引用,只要到GC Roots可达,就不会被回收;

软引用:对象在将要发生内存溢出之前,会被回收;

弱引用:对象在下一次GC时被回收;

虚引用:形同虚设,虚引用的对象,可以视为GC Roots不可达的对象。

不可达就一定会回收?

根搜索算法中不可达的对象也并非是‘非死不可’的,暂时是‘缓刑’阶段,要真正判断一个对象死亡要经历两次标记过程:如果对象在进行根搜索后发现对象不可达,那它将会进行被第一次标记并且进行筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法或者finalize()方法已经被虚拟机掉用过,这两种情况都视为‘没有必要执行’。

如果对象被认为有必要执行finalize()方法,那么这个方法会被放置在一个名为F-Queue的队列之中,并在稍后由一条由虚拟机自动建立的、低优先级的Finalizer线程去执行。这里的‘执行’也只是指虚拟机会触发这个方法,但并不承诺一定会执行。

finalize()方法是对象逃脱死亡命运的最后一次机会,稍后GC会对F-Queue中的对象进行第二次小规模的标记,如果对象在finalize()中重新与引用链上的任何一个对象建立了关联,就会被移出‘即将回收’集合,如果没有移出,那就真的离死亡不远了。

finalize()方法只会被系统自动调用一次。

参考资料

《深入理解Java虚拟机》 周志明

猜你喜欢

转载自blog.csdn.net/u013667756/article/details/82943679
今日推荐