JVM垃圾回收算法
标签:JVM

对象回收

堆分配情况

虚拟机把内存划分为三个区域:新生代(Young Generation),老年代(Old Generation),持久代(Permanent Generation)

回收判断

引用计数法

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

缺点:

  1. 当对象之间存在循环引用时,会导致它们的引用计数器值都不为0,无法被回收。
  2. 引用计数要求在每次因引用产生和消除的时候,需要进行一个加法和减法的操作,可能会影响系统性能

可达性分析算法

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

GC Roots包括以下几种:

方法区的回收

Hotspot中的永久代,永久代中的回收主要有两部分:废弃常量无用的类

废弃常量
当没有任何其他地方引用了这个字面量就可以回收了

无用的类

  1. 该类的所有实例都已经被回收了,也就是Java中不存在该类的任何实例
  2. 加载该类的ClassLoader已经被回收了
  3. 该类对应的Java.lang.Class对象没有在任何地方被引用,无法再任何地方通过反射访问该类的方法。

虚拟机可以对满足上诉三个条件的类进行回收,但并不是和对象一样,不使用了就回收。

在大量使用反射,动态代理,CGLib等ByteCode框架,动态生成JSP以及OSGi这类频繁自定义ClassLoader的场景都需要虚拟机具备类卸载的能力,以保证永久代不会溢出。

垃圾收集算法

标记-清除(Mark-Sweep)

两个阶段:①标记出所有需要回收的对象,②标记完成后,统一进行回收

缺点:

  1. 效率问题:标记和清除的效率都不高
  2. 标记清除将会产生大量不连续的内存碎片,空间碎片太多将会导致分配大对象的时候,没有足够的连续空间,不得不提前触发垃圾回收动作

复制(Copying)

将可用内存分为两块,每次只使用其中一块,当这一块使用完后,就将还活着的对象复制到另一块上去,之后,清除正在使用的这边的内存块中的对象,然后,交换两个内存的角色,不用考虑内存碎片等问题。

新生代串行回收器采用这种算法(因为垃圾对象通常多于存活对象)来回收,Eden 8 ,from 1 ,to 1。

标记-整理(Mark-Compact)

标记过程和标记-清除算法一样,后面不是对可回收对象直接进行清理,而是让存活的对象都移动到一端,然后将边界以外的直接清理掉。

标记-整理适合发生在老年代,大部分对象都是存活对象。如果用复制算法,复制成本较高。

分代算法(Generational Collecting)

复制标记-清除标记-整理,都没办法完全替代其他算法,他们有各自的优缺点。

分代算法,它将内存区间根据对象的特点分成几块,根据每块内存区间的特点,使用不同的回收算法。以提高垃圾回收的效率。新生代使用复制算法,老年代使用标记-清除或者标记-清理

新生代回收频率高,耗时段,老年代回收频率低,耗时长。为了适应新生代的回收,虚拟机使用一种叫卡表(Card Table)的数据结构。卡表是一个比特位集合。每一位可以表示4KB的老年代区域,是否持有新生代对象的引用,当标记位为0,表示不持有,为1的时候表示持有。当垃圾回收时,只需要扫描卡表标记位为1 的区域,可以大幅提高效率。

分区算法(Region)

分区算法将整个堆空间分为连续的不同的小区,每一个小区独立使用,独立回收,这种算法好处可以控制一次回收多少个小区域。

  • 5 min read

CONTRIBUTORS


  • 5 min read