jvm回收对象

jvm在判断对象死亡之前需要判断对象是否可到达,方法有引用计数算法和可达性分析算法,jvm采用的是后者.首先来了解一下这两种算法.

引用计数算法:

算法定义

         为每个对象增加一个字段记录被引用的次数,并由运行时跟踪和更新引用的总数;

object p = new ComparableInt32(57);
 
object q = p;

我们实例化了一个对象ComparableInt32,并将其赋值给变量p,此时p引用了该对象,所以其计数器为1;然后我们又用p给变量q赋值,此时q也引用了该变量,所以其计数器变为2;如下图所示

 

 

从上图我们可以看到,引用类型每次赋值都需要运行时更新计数器,运行时的更新代码可能如下

if (p != q)

{

         if (p != null)

                   --p.refCount;

         p = q;

         if (p != null)

                   ++p.refCount;

}

 

计数器算法的一大优势就是不用等待内存不够用的时候,才进行垃圾的回收,其可以在赋值操作的同时,检查计数器是否为0,如果是的话就可以立即回收;运行时的代码可能如下

if (p != q)

{

         if (p != null)

                   if (--p.refCount == 0)

                            heap.Release(p);

         p = q;

         if (p != null)

                   ++p.refCount;

}

计数器算法的一大缺点就是不能解决循环引用的问题;如下图,我们构造了一个列表,我们将最后一个元素的next属性指向第一个元素,即引用第一个元素,从而构成循环引用;这个时候如果我们将列表的头head赋值为null,此时列表的各个元素的计数器都不为0,同时我们也失去了对列表的引用控制,从而导致列表元素不能被回收!

 

 可达性分析算法:

算法特点

1.       需要单独的字段存储计数器,增加了存储空间的开销;

2.       每次赋值都需要更新计数器,增加了时间开销;

3.       垃圾对象便于辨识,只要计数器为0,就可作为垃圾回收;

4.       及时回收垃圾,没有延迟性;

5.       不能解决循环引用的问题;

在主流的商用程序语言(Java、C#,甚至包括前面提到的古老的Lisp)的主流实现中,都是称通过可达性分析(Reachability Analysis)来判定对象是否存活的。这个算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。如图3-1所示,对象object 5、object 6、object 7虽然互相有关联,但是它们到GC Roots是不可达的,所以它们将会被判定为是可回收的对象。

在Java语言中,可作为GC Roots的对象包括下面几种:

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

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

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

本地方法栈中JNI(即一般说的Native方法)引用的对象。

在确定对象不可到达之后,该对象并不会立刻被释放,jvm会判断该对象是否有finalize()方法,若有,则开启一个优先级较低的线程去执行这个方法.如果在执行的过程中,该对象又与引用链上的其他对象建立了关联,则该对象便获得重生,不会被释放.否则,在执行之后被jvm释放.

不过每个对象的finalize()方法仅能执行一次,若一个对象在第一次GC时已经执行了finalize()方法,且获得了重生,则下一次GC时若该对象为不可到达,则直接被释放.(建议:尽量不要使用finalize()方法).

原文地址:https://www.cnblogs.com/vinozly/p/5076875.html