深入理解 Java 虚拟机:JVM 中的 GC 垃圾收集器总结
前言
垃圾收集器是对应垃圾收集算法的具体实现,并且不同的垃圾收集器对应不同应用场景,会有不同的组合。
因此并不存在完美的垃圾收集器,需要通过了解垃圾收集器的特性与组合,才能在调优时选择最适合的收集器。
新生代收集器
Serial 收集器
Serial 收集器
是最基本
、发展历史最悠久的收集器。(老古董?)
特点: 单线程的收集器
垃圾收集算法: 复制算法
优点: 简单而高效
,对于限定单个 CPU 环境来说,由于没有线程交互的开销,专心做垃圾收集从而获得最高的单线程收集效率。
缺点: 它进行垃圾收集时,必须暂停其他所有的工作线程(即 Stop The World)
,直到它收集结束。(用户体验很差)
应用场景: Serial
收集器对于运行在 Client 模式
下的虚拟机来说是一个很好的选择。
组合: Serial 收集器(新生代)
+ Serial Old 收集器(老年代)
ParNew 收集器
ParNew 收集器
是 Serial 收集器
的多线程版本
,因此所有可用的参数,收集算法,Stop The World
,对象分片规则,回收策略都与 Serial 收集器一样
特点: 多线程收集器
垃圾收集算法: 复制算法
缺点: 它进行垃圾收集时,必须暂停其他所有的工作线程(即 Stop The World)
,直到它收集结束。(用户体验很差)
线程数: 默认开启的收集线程数与 CPU 数相同
VM 指令:
- -XX:+UseConcMarkSweepGC: 开启这个参数后,默认使用
ParNew 收集器
- -XX:+UseParNewGC: 强制使用
ParNew 收集器
- -XX:ParallelGCThreads: 指定收集线程数量
组合:
ParNew 收集器(新生代)
+Serial Old 收集器(老年代)
ParNew 收集器(新生代)
+CMS 收集器(老年代)
Parallel Scavenge 收集器
Parallel Scavenge 是一个新生代收集器,又叫吞吐量优先收集器
特点: 并行的多线程收集器,
这个收集器的目标准则是达到一个可控制的吞吐量
。
停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验;
而高吞吐量能高效的利用 CPU 时间,尽快的完成程序的运行任务,主要适合在后台运算而不需要太多交互的任务。
吞吐量 = 运行用户代码时间 /(运行用户代码时间 + 垃圾收集时间)
PS:虚拟机运行 100 分钟,其中垃圾回收用时 1 分钟,吞吐量就是 99%
垃圾收集算法: 复制算法
VM 指令:
- -XX:MaxGCPauseMillis: 控制最大垃圾收集停顿时间,单位毫秒
- -XX:GCTimeRatio: 直接设置吞吐量大小,该值 0 < x < 100 的整数
即最大 GC 停顿时间为 1 / (1 + x),默认值为 99 - -XX:+UseAdaptiveSizePolicy: 当这个参数开启后,即开启了 GC 自适应策略,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调节参数以适应停顿的时间或最大吞吐量
应用场景: 注重高吞吐量,CPU 资源敏感的系统,比如大数据统计计算系统
组合:
Parallel Scavenge 收集器(新生代)
+Serial Old 收集器(老年代)
JDK 1.6 之前,别无选择Parallel Scavenge 收集器(新生代)
+Parallel Old 收集器(老年代)
JDK 1.6 开始
老年代收集器
Serial Old 收集器
Serial Old 收集器
是 Serial 收集器
的老年代版本,JDK 1.6 开始提供
特点: 单线程的收集器
垃圾收集算法: 标记-整理算法
优点: 简单而高效
,对于限定单个 CPU 环境来说,由于没有线程交互的开销,专心做垃圾收集从而获得最高的单线程收集效率。(同 Serial 收集器)
缺点: 它进行垃圾收集时,必须暂停其他所有的工作线程(即 Stop The World)
,直到它收集结束。(同 Serial 收集器)
应用场景:
- 运行在
Client 模式
下的虚拟机。(同 Serial 收集器) Server 模式下
,CMS 收集器
的后备方案,在并发发生Concurrent Mode Failure
时使用。Server 模式下
,JDK 1.5 及以前版本中与Parallel Scavenge 收集器
搭配使用。(也就是说现在不用了)
组合: Serial 收集器(新生代)
+ Serial Old 收集器(老年代)
Parallel Old 收集器
Parallel Old 收集器
是 Parallel Scavenge 收集器
的老年代版本
特点: 多线程收集器
。
垃圾收集算法: 标记-整理算法
应用场景: 注重高吞吐量,CPU 资源敏感的系统,比如大数据统计计算系统
组合: Parallel Scavenge 收集器(新生代)
+ Parallel Old 收集器(老年代)
CMS 收集器
CMS 收集器
是一种获取最短回收停顿时间为目标的收集器。
特点: 重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验
垃圾收集算法: 标记-清除算法
步骤:
- 初始标记: 标记
GC Root
能直接关联的对象,速度很快 - 并发标记: 进行
GC Root Tracing
过程,与并发清除一同工作,耗时长 - 重新标记: 修正并发标记期间因用户程序运行导致的标记产生变动的那一部分对象的标记记录
- 并发清除: 垃圾回收,与并发标记一同工作,耗时长
优点: 并发收集,低停顿
。整个回收中,耗时最长的 并发标记 与 并发清除 都可以与用户线程并发运行
缺点:
- 并发阶段,虽然不会停顿用户线程,但是会
占用一部分 CPU 资源,导致应用程序变慢,总吞吐量降低
。默认线程数是 (CPU 数量 + 3)/ 4,当 4 个 CPU 以上时,并发回收垃圾收集线程占用资源不小于 25%,随 CPU 数量增加而下降。 CMS 收集器
无法处理浮动的垃圾 (标记阶段后又产生的垃圾,只能放下次 GC 回收),可能出现 Concurrent Mode Failure 导致 Full GC。- 由于是并发回收,因此老年代不能等几乎满了再去回收,需要 预留一部分空间提供并发收集时的程序运作使用。
- Concurrent Mode Failure 失败就会导致启用后备方案
Serial Old 收集器
进行老年代垃圾回收,停顿更久。 - 基于
标记-清除算法
实现的回收,因此 存在大量空间碎片的产生,会给大对象分配带来大麻烦
VM 指令:
- -XX:CMSInitiatingOccupancyFraction: 老年代使用阈值,达到后启用 GC,设置太高可能导致 Concurrent Mode Failure,JDK 1.5 默认值为 68%,JDK 1.6 默认值为 92%。
- -XX:+UseCMSCompactAtFullCollection: 开关参数,默认开启,用于在
CMS 收集器
顶不住要进行 Full GC 时,开启内存碎片的合并整理过程。 - -XX:CMSFullGCsBeforeCompaction: 设置执行多少次不压缩的 Full GC 后,跟着来一次带压缩的。(默认值为 0,表示每次进行 Full GC 都进行碎片整理)
应用场景: Java 应用集中在互联网站或 B/S 系统的服务端上
组合: ParNew 收集器(新生代)
+ CMS 收集器(老年代)
两用收集器
G1 收集器
G1 收集器是一款 面向服务端应用的垃圾收集器 ,JDK 1.7 中被视为最前沿的成果之一,JDK 1.9 默认垃圾收集器为 G1
特点:
- 并行与并发: 充分利用 CPU 资源、多核环境下的硬件优势,使用多 CPU 来缩短 Stop-The-World 的时间,且
G1 收集器
依然能通过并发的方式让 Java 代码继续运行。 - 分代收集: 依然保留 分代收集 的思想,且无需与其他收集器配合,能够 独立管理整个 GC 堆。
- 空间整合: 基于
标记-整理
算法实现,不会产生空间内存碎片。 - 可预测与停顿: 能建立 可预测的停顿时间模型,使用者能明确指定在一个长度为 M 毫秒的时间内,消耗在垃圾回收上的时间不超过 N 毫秒
Region 区域:
G1 将内存分为多个大小相等的独立区域 Region,虽然保留新生代,老年代的概念,但是已经不是物理上的隔离,他们都是一部分 Region(无需连续) 的集合。
G1 跟踪各个 Region 里面的垃圾堆积的价值大小,在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region
垃圾收集算法: 标记-整理算法
步骤:
- 初始标记: 标记
GC Roots
能直接管理到的对象,并且修改 TAMS 的值,让下一阶段用户程序并发执行时,能在正确可用的 Region 中创建对象,需要停顿线程,耗时短。 - 并发标记: 从
GC Roots
开始对堆中的对象,进行可达性分析,找出存活东西,这阶段耗时较长,可与用户程序并发执行。 - 最终标记: 修正在 并发标记 期间导致的产生变动的标记记录,这个时间需要停顿线程,但是可并行执行。
- 筛选回收: 根据 Region 的价值和成本进行排序,执行回收计划。
组合: G1 收集器
无需与其他收集器配合,能够 独立管理整个 GC 堆
各版本默认收集器
JDK1.7 默认垃圾收集器:Parallel Scavenge(新生代)+ Parallel Old(老年代)
JDK1.8 默认垃圾收集器:Parallel Scavenge(新生代)+ Parallel Old(老年代)
JDK1.9 默认垃圾收集器:G1
垃圾收集器选择
纯属个人理解:
注重吞吐量系统:尽可能高效的利用 CPU 资源
- JDK 1.6 及以前:
ParNew 收集器(新生代)
+CMS 收集器(老年代)
- JDK 1.6 及以后:
Parallel Scavenge 收集器(新生代)
+Parallel Old 收集器(老年代)
一般 Java B/S 系统:注重快速响应,提升用户体验
- JDK 1.8 及以前:
ParNew 收集器(新生代)
+CMS 收集器(老年代)
- JDK 1.9 开始:
G1 收集器
常用JVM 参数
垃圾回收统计信息
- -XX:+PrintGC: 打印 GC
- -XX:+PrintGCDetails: 打印GC详细信息
- -XX:+PrintGCTimeStamps: 打印CG发生的时间戳
- -Xloggc:filename: 指定 GC 日志的位置
- -verbose:gc: 输出虚拟机中GC的详细情况
- -XX:+PrintCommandLineFlagsjvm: 参数可查看默认设置收集器类型
堆设置
- -Xms: 初始堆大小
- -Xmx: 最大堆大小
- -Xmn: 新生代大小
- -XX:NewRatio: 设置新生代和老年代的比值。如:为3,表示年轻代与老年代比值为1:3
- -XX:SurvivorRatio: 新生代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:为3,表示Eden: Survivor=3:2,一个Survivor区占整个新生代的1/5
- -XX:MaxTenuringThreshold: 设置转入老年代的存活次数。如果是0,则直接跳过新生代进入老年代
- -XX:PermSize、-XX:MaxPermSize: 分别设置永久代最小大小与最大大小(Java8以前)
- -XX:MetaspaceSize、-XX:MaxMetaspaceSize: 分别设置元空间最小大小与最大大小(Java8以后)
收集器设置
- -XX:+UseSerialGC: 设置串行收集器
- -XX:+UseParallelGC: 设置并行收集器
- -XX:+UseParalledlOldGC: 设置并行老年代收集器
- -XX:+UseConcMarkSweepGC: 设置并发收集器
并行收集器设置
- -XX:ParallelGCThreads=n: 设置并行收集器收集时使用的CPU数。并行收集线程数。
- -XX:MaxGCPauseMillis=n: 设置并行收集最大暂停时间
- -XX:GCTimeRatio=n: 设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n) 并发收集器设置
- -XX:+CMSIncrementalMode: 设置为增量模式。适用于单CPU情况。
- -XX:ParallelGCThreads=n: 设置并发收集器新生代收集方式为并行收集时,使用的CPU数。并行收集线程数。