JVM 对象判定和可回收算法

一、可回收对象判定方法

1、引用计数算法

基本原理:

给对象添加一个引用计数器,当有新的地方引用他的时候就将其+1,引用失效则对其-1,知道为0时,说明该对象不被任何引用,可被 GC 回收,如下图所示:

引用计数器算法示意图

但是这种算法是有缺陷的,比如当 A、B 对象都没有使用时,其应该被 GC 回收,然后根据引用计数器算法,A、B 对象互相持有对方引用,导致两者引用计数器都为1,所以不会被 GC 回收,图例如下:

引用计数器算法缺陷

一般要想解决此问题需要在语言层面作支持,比如 ObjectC 语言,其引用中会带有明确的关键字(wait)等,来说明当前引用状态;比如 wait的引用不会导致引用计数器+1等。

由于 Java 并未在语言层面对此算法进行支持,所以主流 JVM 采用的是 科达信分析算法。

2、可达性分析算法

基本原来:

通过一系列称之为 “GC Roots” 的根对象作为起点,从这些对象开始向下搜索遍历,所有搜索过的路径称之为引用链(Reference Chain),当一个对象到 GC Roots 没有任何一个引用链相连(对象不可达)时,证明该对象已不再被引用(失效),此时该对象将被 GC 回收,如下图所示:

可达性分析算法

可作为 GC Roots 的条件:

  • 在虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 在方法区中静态属性引用的对象
  • 在方法区中常量引用的对象
  • 在本地方法栈中JNI(Native方法)引用的对象

二、垃圾收集算法

1、标记-清除算法

原理:算法分为 “标记” 和 “清除” 两个阶段,首先标记出所有需要回收的对象,在标记完成后,统一回收所有被标记的对象;示意图如下:

标记清除算法

缺陷:标记清除之后会产生大量的内存碎片,从而在后续的大对象内存分配需要大的连续空间时没有足够的连续空间来分配;从而导致不得不触发第二次垃圾回收,增加垃圾回收的频度;另外,标记和清除算法也并不高效。

2、复制算法

原理:首先将可用内存划分为2部分,在首次内存使用时,只使用其中一部分,当发生内存回收时,首先将存活对象在另一部分未使用空间中复制一份,然后移动栈顶指针,最后将待回收部分内存完全清空;示意图如下:

复制算法

缺陷:内存利用率低,只能使用其中一半的内存。

现在的商用虚拟机普遍都用这种方法 对新生代 进行内存回收,由于新生代中的对象都是朝生夕死的,所以实际内存比例不必1:1,一半将内存划分为一块较大的使用空间和2块较小的拯救空间,在 JVM 运行时,只会使用一块较大的内存空间和一块较小的拯救空间,当 GC 时,复制两块内存中的存活对象到另一块拯救空间,然后释放两块空间即可。

HotSpot 默认使用空间和拯救空间比例是8:1,这样最大可使用内存将是 90%,但是并不是每次 GC 时存活下来的对象都不会超过剩下的 10%,这是可能就需要比如老年代中的内存做内存担保。

3、标记-整理算法

原理:其原理与标记-清除算法基本一致,只不过标记后并不直接回收对象,而是移动对象到统一的一侧,然后释放边界以外的内存,这种方法在老年代中应用较广;示意图如下:

标记整理算法

缺陷:对 JVM 停顿时间较长

4、分代收集算法

原理:根据对象存货周期不同,将内存划分为几块进行回收;一般把 Java 堆划分为新生代和老年代,然后根据各个周期特点进行回收;该算法目前是商用 JVM 普遍采用的。

本文参考:JVM 自动内存管理:对象判定和回收算法


JVM 对象判定和可回收算法
https://mritd.com/2016/03/23/jvm-object-determination-and-reclaimable-algorithm/
作者
Kovacs
发布于
2016年3月23日
许可协议