JAVA垃圾回收--判断对象是否存活算法(引用计数法和可达性分析法)

      JVM中的堆和方法区主要用来存放对象(方法区中也储存了一些静态变量和全局变量等信息),那么我们要使用GC算法对其进行回收时首先要考虑的就是该对象是否应该被回收。即判断该对象是否还有其他的引用或者关联使得该对象处于存活状态,我们需要将不在存活状态的对象标记出,以便GC回收。

一、引用计数法(reference-counting)

          引用计数法在对象头处维护一个counter,每增加一次对该对象的引用计数器自加1,如果对该对象的引用失联,则计数器自减1。当counter为0时,表明该对象已经被废弃,是垃圾对象,可以被GC回收。但是这种方式一方面无法区分强、软、弱、虚引用类别。另一方面,会造成死锁。假设两个对象相互引用始终无法释放counter,永远不能GC。

    (1)强引用(Strong Reference)

       强引用就是程序代码中普遍存在的,类似“Object obj = new Object()”的引用。只要强引用还在,垃圾回收器永远不会回收掉被引用的对象。

     (2)软引用(Soft Reference)

       软引用是描述一些还有用,但并非必须的对象。对于软引用关联着的对象,在系统将要发生内存溢出之前,将会把这些对象列进回收范围之内,并进行第二次回收,如果还是没有足够的内存,就会抛出内存溢出异常。

     (3)弱引用(Weak Reference)

       弱引用也是描述非必须对象的,但是它的强度比软引用更弱,被弱引用关联的对象只能生存到下一次垃圾回收发生之前。当垃圾回收器工作时,无论当前内存是否足够,都会回收只被弱引用关联的对象。

     (4)虚引用(PhantomReference)

       虚引用也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是希望能在回收这个对象时收到一个系统通知。

二、可达性分析法(GC Roots Tracing)

       通过一系列为GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明该对象是不可用的。如果对象在进行可行性分析后发现没有与GC Roots相连的引用链,也不会理解死亡。它会暂时被标记上并且进行一次筛选,筛选的条件是是否与必要执行finalize()方法。如果被判定有必要执行finaliza()方法,就会进入F-Queue队列中,并有一个虚拟机自动建立的、低优先级的线程去执行它。稍后GC将对F-Queue中的对象进行第二次小规模标记。如果这时还是没有新的关联出现,那基本上就真的被回收了。

 

-----------------------------------------------------------------------------------------------------------------------------------------

以下案例参考: https://www.cnblogs.com/igoodful/p/8727241.html

通过一段代码来对比说明引用计数法和可达性分析法的区别。

public class GcDemo{


  public static void main(String [] args ){

        
      GcObject obj1 = new GcObject();  //第一步

      GcObject obj2 = new GcObject();  //第二步

       obj1.instance = obj2;           //第三步
       obj2.instance = obj1;           //第四步

       obj1 = null;                    //第五步
       obj2 = null;                    //第六步
   }

}


class GcObject{

    public Object instance = null;
}

结论:如果采用引用计数法,上述代码中obj1和obj2指向的对象已经不可能再次被访问,彼此互相引用对方导致引用计数都不为0,最终无法被GC回收。而可达性分析法能解决这个问题。

(一)采用引用计数法

   前四步执行:

JAVA垃圾回收--判断对象是否存活算法(引用计数法和可达性分析法)_第1张图片

      第一步:GcObject obj1 = new GcObject();      GcObject实例1的引用计数器加1,实例1的引用数=1;

      第二步:GcObject obj2 = new GcObject();      GcObject实例2的引用计数器加1,实例2的引用数=1;

      第三步:obj1.instance = obj2;                         GcObject实例1的引用计数器再加1,实例1的引用数=2;

      第四步:obj2.instance = obj1;                         GcObject实例2的引用计数器再加1,实例2的引用数=2;

执行完第四步之后,实例1和实例2的引用计数都等于2。

     后两步执行:

JAVA垃圾回收--判断对象是否存活算法(引用计数法和可达性分析法)_第2张图片

     第五步:obj1 = null;      栈帧中obj1不再指向Java堆,GcObject实例1的引用计数减1,结果为1;

     第六步:obj2 = null;      栈帧中obj1不再指向Java堆,GcObject实例1的引用计数减1,结果为1;

至此,GcObject实例1和2的引用计数均不为0,如果采用引用计数法,两个实例所占用的内存得不放释放,会产生内存泄漏。

 

(二)采用可达性分析法

目前主流的虚拟机都是采用可达性分析法(GC Roots Tracing),例如HotSpot虚拟机便采用该算法。该算法的核心算法是从GC Roots对象作为起点,利用数学中图论的知识,图中可达对象便是存活对象,而不可达对象则是可能需要回收的内存垃圾。

   可以作为GC Roots的对象

  • 虚拟机栈的栈帧的局部变量表所引用的对象;
  • 本地方法栈的JNI所引用的对象;
  • 方法区的静态变量和常量所引用的对象;

JAVA垃圾回收--判断对象是否存活算法(引用计数法和可达性分析法)_第3张图片

从上图reference1、reference2、reference3都是GC Roots,可知:

  • reference1  可达  对象实例1
  • reference2  可达  对象实例2
  • reference3  可达  对象实例4   可达   对象实例6

由此可以得出结论:对象实例1、2、4、6都具有GC Roots可达性,也就是存活对象,不能被GC回收。而对象实例3、5虽然直接连通,但是没有任何一个GC Roots与之相连,是GC Roots不可达对象,GC需要回收这种垃圾对象。

 

由此,针对上面例子用可达性分析法判定:

JAVA垃圾回收--判断对象是否存活算法(引用计数法和可达性分析法)_第4张图片

在经过第五步和第六步之后,GcObject1和GcObject2与GC Roots(此时的GC Roots即为栈帧中的局部变量表obj1和obj2)不可达。

即为不可达对象,所以垃圾回收器应该回收。

对于对象之间的循环引用,采用引用计数法,则GC无法回收这两个对象,但是采用可达性分析法就能正确回收该垃圾对象。

 

 

 

本文旨在个人学习,做好笔记供后期复习。

参考文章链接:https://www.cnblogs.com/igoodful/p/8727241.html

 

 

 

你可能感兴趣的:(JVM,java,jvm)