垃圾收集器

如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。

前言

一本好书,每读一遍都会有不同的感受。写读书笔记,一来是便于平时查阅,毕竟技术书籍都比较厚,不方便随时携带;二来是督促自己多读书,理论与实践结合才能不断提升自己。

【深入理解Java虚拟机】阅读笔记: 3.5 垃圾收集器

HotSpot 虚拟机提供了多个作用于不同分代的收集器,如下图所示,如果两个收集器之间存在连线,则说明它们之间可以搭配使用。

HotSpot 虚拟机的垃圾收集器

并行与并发收集器说明

并行 (Parallel)。指多条垃圾收集器并行工作,但此时用户线程仍然处于等待状态
并发 (Concurrent)。指用户线程与垃圾收集线程同时执行 (但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行于另一个 CPU 上

Serial 收集器

  • 最基本、发展历史最悠久的收集器
  • 是一个单线程的收集器,在进行垃圾收集时,必须暂停其他所有的工作线程
  • Client 模式下的默认新生代收集器
  • 简单高效,在单 CPU 环境下,由于没有线程交互的开销,收集效率最高

Serial / Serial Old 收集器运行示意图

ParNew 收集器

  • Serial 收集器的多线程版本
  • 与 Serial 收集器的所有控制参数 (-XX:SurvivorRatio、-XX:PretenureSizeThreshold、-XX:HandlePromotionFailure等)、收集算法、Stop The World、对象分配规则、回收策略等都完全一样
  • Server 模式下首选的新生代收集器
  • 除了 Serial 收集器外,只有它能与 CMS 收集器配合工作

parnew

Parallel Scavenge 收集器

  • 新生代收集器,使用复制算法并行多线程收集
  • 目标是达到一个可控制的吞吐量,适合在后台运算而不需要太多交互的任务。吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)
  • -XX:MaxGCPauseMills 参数用于控制最大垃圾收集时间,参数值为一个大于 0 的毫秒数。收集器将尽可能地保证内存回收花费的时间不超过设定值
  • -XX:GCTimeRatio 参数用于设置吞吐量,参数值为一个大于 0 且 小于 100 的整数,表示垃圾收集时间占总时间的比例。默认值为 99,即允许最大 1% (1 / (1+99))的垃圾收集时间
  • -XX:+UseAdaptiveSizePolicy 参数打开后不需要制定新生代的大小 (-Xmn)、Eden 与 Survivor 区的比例 (-XX:SurvivorRatio)、晋升老年代对象大小 (-XX:PretenureSizeThreshold) 等细节参数,由虚拟机自动动态调整

Serial Old收集器

  • Serial 收集器的老年代版本,使用单线程和 “标记-整理” 算法
  • Server 模式下:1. 搭配 Parallel Scavenge 收集器使用。2. 作为 CMS 收集器的后备预案

Parallel Old 收集器

  • Parallel Scavenge 收集器的老年代版本,使用多线程和 “标记-整理” 算法
  • 吞吐量优先收集器

parallel

CMS 收集器

运作过程分为 4 个步骤

  • 初始标记 (CMS initial mark)。需要 “Stop The World”,标记 GC Roots 能直接关联到的对象
  • 并发标记 (CMS concurrent mark)。进行 GC Roots Tracing
  • 重新标记 (CMS remark)。需要 “Stop The World”,修正并发标记期间因用户程序继续运行而导致的标记变动记录
  • 并发清除 (CMS concurrent sweep)

cms

优点

  • 并发收集。耗时最长的并发标记和并发清除过程,收集器线程可以与用户线程一起工作
  • 低停顿

缺点

  1. 对 CPU 资源非常敏感。默认启动回收线程数是 (CPU 数量 + 3) /4,当 CPU 数 不足4个时,对用户程序的影响可能变得很大
  2. 无法处理浮动垃圾 (Floating Garbage),可能出现 “Concurrent Mode Failure” 失败而导致另一次 Full GC 的产生,这时将临时启用 Serial Old收集器重新进行老年代的垃圾回收。因此 -XX:CMSInitiationOccupancyFraction (老年代使用了百分之多少时触发 CMS 垃圾回收) 设置太高会导致性能降低
  3. 基于 “标记-清除” 算法,收集结束时会有大量空间碎片产生。**-XX: +UseCMSCompactAtFullCollection** (要进行 FullGC 时开启内存碎片整理)。**-XX:CMSFullGCsBeforeCompaction** (执行多少次不压缩的 FullGC 后来一次带压缩的,默认值为 0)

G1 收集器

优点

  1. 并行与并发。使用多个 CPU 来缩短 “Stop The World” 停顿的时间,部分其他收集器需要停顿的 GC 动作,G1 收集器可通过并发的方式让用户程序继续执行
  2. 分代收集。采用不同方式处理新对象和已经存活一段时间、熬过多次 GC 的旧对象
  3. 空间整合。整体上基于 “标记-整理” 算法,局部上基于 “复制” 算法,不会产生内存空间碎片
  4. 可预测的停顿。建立可预测的停顿时间模型,让使用者指定在 M 毫秒的时间片段内,垃圾收集时间不超过 N 毫秒

执行步骤

不考虑维护 Remembered Set的操作,大致可划分为以下几个步骤

  1. 初始标记 (Initial Marking)
  2. 并发标记 (Concurrent Marking)
  3. 最终标记 (Final marking)
  4. 筛选回收 (Live Data Counting and Evacuation)

g1

理解 GC 日志

gc-log1

GC 发生的时间: [垃圾收集的停顿类型 [GC 发生的区域: GC 前该内存区域已使用容量 -> GC 后该内存区域已使用容量 (该内存区域总容量), 该内存区域 GC 所用时间] GC 前 Java 堆已使用容量 -> GC 后 Java 堆已使用容量 (Java 堆总容量), Java 堆 GC 所用时间]

垃圾收集器参数

参数描述
UseSerialGC使用 Serial + Serial Old 收集器组合
UseParNewGC使用 ParNew + Serial Old 收集器组合
UseConcMarkSweepGC使用 ParNew + CMS + Serial Old收集器组合。CMS 收集器出现 Concurrent Mode Failure 时使用Serial Old回收内存
UseParallelGC使用 Parallel Scavenge + Serial Old (PS MarkSweep) 收集器组合
UseParallelOldGC使用 Parallel Scavenge + Parallel Old 收集器组合
SurvivorRatio新生代中 Eden 区域 与 Survivor 区域的容量比值,默认为 8,代表 Eden : Survivor = 8 : 1
PretenureSizeThreshold直接晋升到老年代的对象大小,大于这个参数的对象将直接在老年代分配
MaxTenuringThreshold晋升到老年代的对象年龄。每个对象经过一次 Minor GC 后年龄加 1,当超过这个参数时就进入老年代
UseAdaptiveSizePolicy动态调整 Java 堆中各个区域的大小以及进入老年代的年龄
HandlePromotionFailure是否允许分配担保失败,即老年代的剩余空间不足以应付新生代的整个 Eden 和 Survivor 区的所有对象都存活的极端情况
ParallelGCThreads设置并行 GC 时进行内存回收的线程数
GCTimeRatioGC 时间栈总时间的比率,默认 99,即允许 1% 的GC时间,仅在使用 Parallel Scavenge 收集器时生效
MaxGCPauseMills设置 GC 的最大停顿时间,仅在使用 Parallel Scavenge 收集器时生效
CMSInitiatingOccupancyFraction设置 CMS 收集器在老年代空间被使用多少后触发垃圾收集
UseCMSCompactAtFullCollection设置 CMS 收集器在完成垃圾收集后是否要进行一次内存碎片整理
CMSFullGCsBeforeCompaction设置 CMS 收集器在进行若干次垃圾收集后再启动一次内存碎片整理

参考

周志明. 深入理解Java虚拟机