xzhuz / blog-gitment

博客备份和comment记录
https://meisen.pro
0 stars 0 forks source link

引用计数法和可达性分析算法 #44

Open xzhuz opened 4 years ago

xzhuz commented 4 years ago

https://hshanx.github.io/gKqr1JUi4/

困而学,学而知

好记性不如烂笔头

紧接着上一篇JVM老生常谈之运行时数据区,我们已经连接了Java虚拟机几个运行时数据区,今天我们接着来讲讲Java虚拟机的几个重要的内存回收算法。本文所涉及的知识基本都基于HotSpot虚拟机。

首先,我们先来认识两个普遍用于判断对象是否被引用的算法:引用计数法可达性算法

引用计数法其实很简单,如果对象的计数器为0,就说明对象不再被引用,否则就是再被引用。

可达性算法则是通过判断对象是否能够被GC ROOT访问到来判断对象是否还在被引用。

引用计数法

给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1,当引用失效时,计数器就减1。任何时刻计数器为0的对象就是不再被使用的。

但是引用计数法其实是很难解决对象之间相互循环引用的问题,所以,Java虚拟机里面没有选用引用计数算法来管理内存。

我们用下面的例子来验证一下上面说的是否正确。

    public class ReferenceCountingGC {
    public Object instance = null;
    private static final int _1MB = 1024 * 1024;
    /**
     * 这个成员属性的唯一意思就是占点内存,以便能在GC日志中看清楚是否被回收过
     */
    private byte[] bigSize = new byte[2* _1MB];

    public static void testGC(){
      ReferenceCountingGC objA = new ReferenceCountingGC();
      ReferenceCountingGC objB = new ReferenceCountingGC();
      objA.instance = objB;
      objB.instance = objA;

      //假设在这行发生gc,objA和ObjB是否能被回收
      System.gc();
    }
  }

GC输出

可以看到,其实也是有被回收了,也就是意味着虚拟机并没有因为两个对象相互引用就不回收他们。侧面说明虚拟机并不是通过引用计数法来判断对象是否存活。

虽然可引用计数法很简单,也经常被提及,但是HotSpot虚拟机却不是用这个算法来判断对象是否继续被引用,而是使用下面要介绍的算法:可达性分析算法。

可达性分析算法

在主流商用程序语言的主流视线中,都是称通过可达性分析来判定对象是否存活的。

算法的基本思路就是通过一系列称为GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径被称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象不可用的。

可达性分析算法示例

上图中,Object1~Object4都可以被GC Root访问到,而Object5~Object7都不可以被访问到,这也就是说。也就是说,Object5、6、7这三个对象就是不可达的,下次垃圾回收的时候,可能就会被回收掉。

其实并不是所有的对象都可以作为GC Roots的对象,只有下列的对象可以作为GC Roots的对象。

可以作为GC Roots的对象

既然是引用计数法,那肯定就有各种引用,下面来说说一些引用。

强引用、软引用、弱引用和虚引用

JDK1.2之后,Java对引用的概念进行了扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)4种。

JDK1.2之前,只有被引用和没有被引用两种状态

要宣告一个对象死亡,至少要经历两次标记过程:

  1. 第一次标记: 如果想在进行可达性分析发生没有 GCRoots 相连接的引用连,那么它将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都视为没有必要执行。否者就是有必要执行,则会被放到一个F-Queue队列。

  2. 第二次标记:finalize()方法是对象跳脱死亡命运的最后一次机会,稍后GC将对F-Queue中对象进行第二次小规模标记,如果对象要在finalize()中称重拯救自己-只要重新与引用链上的任何一个对象建立关联即可,譬如把自己(this关键字)赋值给某个类变量或者对象的成员变量,那在第二次标记时她将被一簇“即将回收”的集合。

简单总结

简单的对上面做一个总结,在JVM中判断一个对象是都需要回收有两种算法:引用计数法和可达性算法。引用计数法是通过判断引用的计数器的值是否为0来确认回收与否。这种算法听起来很简单,但是存在一个缺陷,就可以可能存在循环引用的情况。

还有一种就是可达性算法,可达性算法是通过判断引用能够被 GC Roots 访问到来确认回收与否。能被称为GC Roots对象也是有条件的主要有四种:虚拟机栈中引用的对象、方法中类静态属性引用的对象、方法中常量引用的对象和本地方法栈(native方法)中JNI引用的对象。

引用分为四种类型:强引用、软引用、弱引用和虚引用。