理解JVM的GC机制之垃圾判定

前言

JVM 的内存划分规范中,我们可以知道除了方法去和堆区外其他的内存区域都是线程私有的,这就意味着,这些区域是跟随线程而生而灭的,自然不是 GC 的作用区域。然而堆区、方法区却是跟随 JVM 实例的生命周期而来的,多个线程共享这块区域,是 GC 的主要作用区域。下面将会以堆区为当前内存区域进行说明。

那么在堆区存放着 JVM 实例中几乎全部的对象,这就需要判断哪些对象是可以回收的垃圾对象,哪些对象是不可回收的存活对象。

垃圾对象判定算法

判断对象是否存活的算法也就是垃圾对象判定算法,目前主要是有两种主流算法。

## 引用计数法

引用计数法顾名思义就是计算对象的被引用数。

我们为每个对象设置一个计数器,每当有其他地方引用该对象时计数器加 1,引用失效则减 1,当计数器为 0 的时候,就可以判断该对象失效,为垃圾对象。

引用计数.png

这种算法一大优点就是实现简单,仅需为对象设置计数器即可;判定效率较高,仅遍历出回收区域内计数器值为 0 的对象即可。但该算法也存在着一个重大的缺陷,如果出现下面的情况

循环引用.png

对象 A 与对象 B 之间出现了相互引用,对象 A、B 的计数器值因为对方引用的存在都不会减到 0,永远都不会被判定为垃圾对象。对于这种情况,简单的引用计数判断是很难下手的,需要进行额外的判断逻辑。

## 可达性分析法

可达性分析是目前 JVM 中所采用的垃圾判定算法。可达性分析就是设定一些对象为GC Roots对象,以 GC Roots 对象为起始点,向下搜索遍历,遍历过程中所走过的路径即为应用链。当一个对象没有任何一条引用链可以到达 GC Roots 时,就可以任务该对象是不可到达的,就可以判定该对象是垃圾对象;相反的,在任何引用链中包含的对象可判定为可达对象。

可达性分析.png

这种判定方式便可轻松解决掉循环引用的问题,判定的主要因素仅是对象是否可以上溯到 GC Roots 对象,即便是循环引用的对象只要无法向上追溯到 GC Roots 便可认为是垃圾对象。

那么哪些对象可以作为 GC Roots 的对象呢,从 JVM 内存划分中我们可以知道,栈区(包括虚拟机栈、本地方法栈)中的对象是线程私有的对象,不论线程是什么状态的线程,主要线程存活未销毁,我们都可以认为线程栈中的对象都是活跃对象。同样的,方法区中的常量对象、已加载的静态类属性等跟随 JVM 实例生命周期而存活的对象都可以是活跃对象。所以,我们可以简单的认为以下的对象可作为 GC Roots 对象:

  1. 虚拟机栈中引用的对象
  2. 本地方法栈中引用的对象
  3. 方法区中类静态属性引用的对象
  4. 方法区中常量引用的对象

然而准确点的来说,GC Roots 并不是一组活跃的对象集合而是一组活跃的引用集合。从这些跟引用出发,通过引用关系遍历对象,能够被遍历到的对象就被判定为存活对象,其余未被遍历到的对象就被判定为垃圾对象。

那么相应的,可以作为 GC Roots 引用的包括:

  1. 虚拟机栈、本地方法栈中指向堆对象的引用
  2. 方法区中引用类型的类静态属性的引用
  3. 方法区中引用类型常量的引用

目前的主流语言大部分采用的都是可达性分析算法。

原文地址:https://www.cnblogs.com/tingyugetc/p/12828218.html