Java垃圾收集器

kolbe 2021年09月14日 100次浏览

1 对象存活判断

在Java堆中存放的对象,垃圾收集器工作时,需要判定哪些还活着,哪些已死去,从而对死去的对象执行回收,常用的对象存活判断方法如下

1.1 引用计数算法

这种算法给对象添加一个引用计数器,当有地方引用时,计数值加1,当引用失效时,计数值减1,当计数器为0时,对象就不在被使用。这种算法的优点在于实现简单,判定效率高。缺点在于很难解决对象之间的循环引用问题

1.2 可达性分析算法

这种算法通过GC Roots对象为起点,从这些节点往下搜索,搜索经过的路径称为引用链,当一个对象没有被引用链相连时,则证明对象不可用。在Java中GC Roots对象包括以下几种:

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

1.3 引用分类

JDK1.2之后,扩展了引用的概念,以支持当内存空间还足够时,则能保留在内存之中;如果内存空间在进行垃圾收集后还是非常紧张,则可以抛弃这些对象

  • 强引用:强引用指的是new指令出来的引用,只要引用还在,垃圾回收器就不会回收掉对象
  • 软引用:软引用在系统发生内存溢出前,将这些对象进行回收,如果还不够将抛出内存溢出异常
  • 弱引用:弱引用只能存活在下一次垃圾回收之前,不管内存是否足够
  • 虚引用:虚引用不影响对象生存时间,也无法通过虚引用取得对象实例,在虚引用关联的对象被回收时,将会收到一个系统通知

1.4 回收方法区

方法区主要回收两部分内容:废弃常量和无用的类,判定废弃常量是通过判断没有任务String对象引用该常量时,则代表废弃。判定无用的类需要满足以下三个条件:

  • 该类的所有实例都被回收
  • 该类的ClassLoader被回收
  • 该类的Class对象没有被引用

2 垃圾收集算法

2.1 标记清除算法

标记清除算法分为两个阶段

  • 标记:首先标记出所有要回收的对象
  • 清除:在标记完成后,统一清除所有被标记的对象

该算法主要的不足:

  • 效率:标记和清除两个过程效率都不高
  • 空间:标记清除后产生大量内存碎片,碎片太多导致大对象不够分配时,将触发另一个垃圾收集动作

2.2 复制算法

通过将内存划分成两个相同大小的两块,每次只使用其中一块,当一块使用完后,将存活的对象复制到另一块上,然后集中回收使用完的那块。

该算法主要的不足:

  • 空间:可用的内存缩小了一半

该算法主要的优点:

  • 简单:不用考虑内存碎片,只需移动堆顶指针,运行高效

现代商业的虚拟机采用这种算法回收新生代,通过将内存划分一个较大的Eden区,和两个较小的Survivor区,每次只使用Eden区和其中一个Survivor区,在回收时将存活的对象复制到另一个Survivor区,并清除Eden和当前Survivor区,HotSpot虚拟机默认Eden和Survivor比例为8:1:1,所以只会有10%的内存浪费。当Survivor区不够用时,需要依赖老年代进行内存担保

2.3 标记整理算法

标记整理算法分为两个阶段:

  • 标记:首先标记出所有要回收的对象
  • 整理:将所有的存活对象向一端移劝,然后清理掉端边界以外的内存

2.4 分代收集算法

根据对象的存活周期不同将内存划分成几块,一般分为新生代和老年代,根据不同年代的特点,选用不同的算法

  • 新生代:使用复制算法,因为存活的对象不多,只有少量,复制代价小
  • 老年代:使用标记清理或标记整理算法,因为存活对象多,复制代价大

3 垃圾收集器

垃圾收集算法是方法论,垃圾收集器是具体的实现

3.1 Serial收集器

Serial收集器使用复制算法并工作在新生代中,它使用单线程来回收,它只会使用一个CPU或一条收集线程去完成垃圾收集工作,在进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束,常用于运行在Client模式下的虚拟机

3.2 ParNew收集器

ParNew收集器使用复制算法并工作在新生代中,它是Serial收集器的多线程版本,该收集器可以与CMS收集器配合,在单个CPU的环境下表现不一定比Serial好,因为多了线程切换的开销

3.3 Parallel Scavenge收集器

Parallel Scavenge收集器使用复制算法并工作在新生代中,它的目标是达到一个可控制的吞吐量(吞吐量 = 运行用户代码时间 / 总时间;其中总时间 = 运行用户代码时间 + 垃圾收集时间)

3.4 Serial Old收集器

Serial Old收集器使用的是标记整理算法并工作在老年代中,它同样是单线程收集器,可以在JDK 1.5前与Parallel Scavenge收集器搭配,也可以作为CMS收集器的后备方案

3.5 Parallel Old收集器

Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多线程和标记整理算法

3.6 CMS收集器

CMS(Concurrent Mark Sweep)收集器使用的是标记清理算法,工作于老年代中,目标是获取最短的回收停顿时间。
CMS的工作过程分为以下四步:

  • 初始标记:标记GC Roots直接关联的对象
  • 并发标记:进行GC Roots Tracing
  • 重新标记:修正并发标记期间因用户程序继续运行而导致的变动
  • 并发清除

CMS收集器主要的不足:

  • 对CPU资源敏感
  • 无法处理浮动垃圾
  • 无法处理空间碎片

3.7 G1收集器

G1收集器将整个Java堆划分成多个大小相等的独立区域(Region),新生代和老年代不在物理隔离,而是同属于Region。
G1收集器的工作过程如下:

  • 初始标记:标记GC Roots直接关联的对象
  • 并发标记:进行GC Roots Tracing
  • 最终标记:修正并发标记期间因用户程序继续运行而导致的变动
  • 筛选回收:回收阶段首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划

G1收集器主要的特点:

  • 并行与并发:
  • 分代收集:
  • 空间整合:
  • 可预测的停顿:不仅追求低停顿,还能预测停顿时间