文章目录
判断一个对象是否"死亡",是否该被回收—Java虚拟机
1.Java堆中的对象"死亡"判断
java堆中存放着大量的对象实例,而垃圾收集器GC就负责收集堆中的垃圾,可是我们怎么判断一个对象是否已经"死亡" 从而应该被回收呢?
1.1引用计数算法
引用计数算法使用非常容易理解的算法.简单来说,就是在一个对象中加入一个对象引用计数器,每有一个引用指向它,那么计数器就+1.相反如果有一个引用不再指向它,那么计数器就减一.如果一个对象的引用计数器为0.既没有引用指向它,那么GC就会收集它.
引用计数法原理简单,效率也不错,虽然占用了一些额外的空间,但是总体来说还是一个不错的算法.但是它本身有很多额外的情况需要考虑.JVM中也没有使用这种算法.举个出错的例子:
public class GCTest
{
public Object instance=null;
public static void main(String[] args)
{
GCTest gcTest1=new GCTest();//这个对象实例现在有了一个引用
GCTest gcTest2=new GCTest();//这个对象实例现在也有了一个引用
gcTest1.instance=gcTest2;//这个对象实例现在又多了一个引用
gcTest2.instance=gcTest1;//这个对象实例现在也又多了一个引用
gcTest1=null;//对象实例减一
gcTest2=null;//对象实例也减一
}
}
可以看到这里两个实例对象经过一系列操作后,他们的引用计数器都不为0,但是他们还可以被谁给获取到吗?换句话说,他们不符合引用计数法的GC条件,但是他们应该被回收.
1.2可达性分析算法
当前主流的常用程序语言(Java,C#等)的内存管理子系统和都是通过**可达性分析(Reachability Analysis)**算法来判定一个对象是否应该被回收.可达性算法分析的思路就是通过一系列的"GC Roots"的根对象来作为起始节点集,以及从这些节点的引用关系从上往下进行搜索.整个搜索过程所走过的路程被称为"引用链".如果一个对象不在任何的引用链上,或者用图论的说法:GCRoots顶点集与对象点之间不可达.这样就可以证明这个对象是不可能被使用的了.
但是即使一个对象在可达性分析中被判定为可回收的对象.但是这并不意味着该对象就一定要被回收.JVN中如果真的要"处决"一个对象,最多会经历两次审判.
- 第一次就是对该对象进行可达性分析,如果不可达,会被标记一次.然后JVM会对它进行一次筛选,筛选判断该对象有没有必要执行finalize()方法.
- 如果该对象没有覆盖finalize()方法,或者finalize()方法已经被JVM执行过了.那么GC就会回收该对象,就不用进行第二次审判了
- 如果该对象被判定为有必要执行finalize()方法.那么就获得"重生"机会.进入第二步
- 获得第二次机会的对象会被放置在一个名为F-Queue的队列中,并在稍后由一条虚拟机自动建立的低优先级Finalize线程去执行它们的finalize()方法.如果该对象在执行finalize()方法的时候成功拯救自己—与GC Roots建立了连接.那么它就活过来了.
2.方法区中的对象"死亡"判断
与堆中进行垃圾收集相比,方法区中的垃圾收集可以说是"性价比"比较低的.花同样的时间和经历,在堆中进行GC收获的比在方法区多得多.这可能也是为什么<Java虚拟机规范>中没有强制要求虚拟机在方法区进行垃圾收集.但是,虽然效率低但是事情还是要做的.方法区主要收集是两部分内容:
- 废弃的常量
- 不再使用的类型
2.1对废弃的常量进行判定
对常量是否"死亡"的判断与对中对象有些类似.如果一个常量池中的常量
- 没有一个对象的值是它,
- 而且虚拟机中也没有一个地方引用它.
那么如果GC判定有必要对它进行收集的话,就会清理它
2.2判定一个类型是否死亡
想要判断一个类型是否属于"不可再被使用的类",这个条件就相对而言比较苛刻.\
- 该对象的所有实例都被回收了,
- 加载该类的类加载器已经被回收了.
- 该类的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射来访问该类的方法.