垃圾收集器
来自 《深入理解 Java 虚拟机:JVM 高级特性与最佳实践》 读书笔记
如果说垃圾收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。
上图展示了7种作用于不同分代的收集器。下面逐一介绍这些收集器的特性、基本原理、使用场景和部分运作细节。
Serial 收集器
- 新生代收集器,采取复制算法,单线程的收集器;
- “单线程”是指,GC线程工作时,暂停所有用户线程(造成停顿),俗称 “Stop The World”;
- 简单、高效(较其他收集器的单线程比),无线程交互开销;
- 缺点:停顿是硬伤;
- 适用:对于运行在Client模式下的虚拟机来说是个很好的选择;
- ……
Serial Old 收集器
- 是 Serial 收集器的老年代版本,单线程,使用 “标记-整理” 算法;
- 适用:用于给 Client 模式下的虚拟机使用;
- 用途是作为 CMS 收集器的后备预案;
- ……
ParNew 收集器
- 新生代收集器,采用复制算法,多线程的收集器;
- Serial 收集器的多线程版本,即使用多条线程进行垃圾收集(使用 -XX:ParallelGCThreads 参数来限制垃圾收集的线程数);
- 除 Serial 外,目前只有它能与 CMS 收集器配合工作;
- 多核处理器环境下,效果比 Serial 好;
- ……
Parallel Scanvenge 收集器
- 新生代收集器,使用复制算法,并行的多线程收集器;
- 特征:关注的目标是达到一个可控制的吞吐量(Throughput = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间));
- 适合在后台运算而不需要太多交互的任务;
- 自适应调节策略;
- ……
两个用于精确控制吞吐量的参数:
-XX:MaxGCPauseMillis:控制最大垃圾收集停顿时间
-XX:GCTimeRatio:设置吞吐量大小,取值范围(0, 100),单位为%
其他参数:
-XX:+UseAdaptiveSizePolicy:一个开关参数,打开后就不需要手工指定新生代的大小(-Xmn),Eden 与 Survivor 区的比例(-XX:SurvivorRatio),晋升老年代对象大小(-XX:PretenureSizeThreshold)
虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或最大吞吐量,这种调节方式称为GC自适应的调节策略。
Parallel Old 收集器
- 是 Parallel Scavenge 的老年代版本,多线程,“标记-整理” 算法;
- ……
CMS(Concurrent Mark Sweep) 收集器
- 老年代收集器;
- 一种以获取最短回收停顿时间为目标的收集器;
- 基于 “标记-清除” 算法,耗时最长的 “并发标记” 和 “并发清除” 过程可以与用户线程并发执行;
- 优点:并发收集、低停顿,又称之为并发低停顿收集器;
- 缺点有3,具体见后面;
- ……
CMS 基于 “标记-清除” 算法的实现步骤:
- 初始标记:标记 GC Roots 能直接关联到的对象,速度快,产生停顿;
- 并发标记:进行 GC Roots 的过程;
- 重新标记:为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,快于并发标记,长于初始标记,产生停顿;
- 并发清除:回收对象。
CMS 收集器的3个明显的缺点:
- 对 CPU 资源非常敏感。因为并发阶段 GC 线程占用部分 CPU 资源而导致应用程序变慢。
无法处理浮动垃圾,可能出现 “Concurrent Mode Failure” 失败而导致另一次 Full GC 的产生。
- 浮动垃圾是指,由于 CMS “并发清理”阶段用户线程还在运行而产生新的垃圾,且在当次收集中无法处理。为此,需要在老年代预留一部分空间供并发收集时的程序运作使用。相关参数有,
- -XX:CMSInitialtingOccupancyFraction:Full GC 的触发百分比,提高该值可降低内存回收次数从而获取更好的性能。
- 当 CMS 运行期间预留的内存无法满足程序需要,就会出现一次 “Concurrent Mode Failure” 。此时需要启动后备预案:临时启用 Serial Old 收集器来重新进行老年代的垃圾收集。
收集结束后会有大量的空间碎片产生。解决方案:提供两个参数,
- -XX:+UseCMSCompactAtFullCollection:开关参数(默认开启),用于在 CMS 收集器顶不住要进行 FullGC 时开启内存碎片的合并整合过程。
- -XX:CMSFullGCsBeforeCompaction:用于设置执行多少次不压缩的 Full GC 后,接着来一次带压缩的(默认值为0)。
G1(Garbage-First)收集器
- 当今收集器技术发展的最前沿成果之一,一款面向服务端应用的垃圾收集器,使命是替换 CMS 收集器;
- 相较于其他收集器具备新的特点,详情见后面;
- 将内存 “化整为零” 的思想,实现细节是基于 Remembered Set 的(此部分内容在本文暂时没有讨论);
- ……
G1 收集器的特点
- 并行与并发:使用多个 CPU 并行来缩短停顿时间,同时使 GC 线程和其它用户线程并发执行。
- 分代收集:不需要其他收集器配合即可独立管理整个 GC 堆。G1 将整个 Java 堆划分为多个大小相等的独立区域(Region),尽管保留新/老生代的概念,但彼此间不是物理隔离,而是一部分 Region (不连续)的集合。
- 空间整合:整体看是基于 “标记-整理” 算法,局部看是基于 “复制” 算法实现,所以不会产生内存碎片。
- 可预测的停顿:能建立可预测的停顿时间模型,因为它可以有计划地避免在整个 Java 堆中进行全区域的垃圾收集。实现是基于优先级的列表的。
可预测的停顿时间模型的实现过程
G1 跟踪各个 Region 里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region (G1 名字的由来)。这种使用 Region 划分内存空间以及有优先级的区域回收方式,保证了 G1 收集器在有限的时间内可以获取尽可能高的收集效率。
G1 收集器的运作大致可划分为以下几个步骤:
- 初始标记:标记 GC Roots 能直接关联到的对象,需停顿用户线程,耗时短。
- 并发标记:对堆中对象进行可达性分析,耗时长,但可与用户线程并发执行。
- 最终标记:为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,需停顿用户线程,也可并发执行。
- 筛选回收:对各个 Region 的回收价值和成本进行排序,根据用户所期望的 GC 停顿时间来指定回收计划。也可并发执行,但没必要。