前言
Java 是一门不需要开发人员关注对象销毁、内存回收的开发语言。这主要得益于 JVM 中的垃圾收集器,它们会在必要的时候,自动对可不用的对象进行垃圾回收,释放内存,保证程序的持续运行。Java 发布至今,有许多各式各样的垃圾收集器,它们各有优劣,满足于不同应用场景。
测试程序
提供的测试程序,通过设置不同的虚拟机参数,以观察回收日志输出情况。
新生代
1 |
|
老年代
1 |
|
新生代
Serial
Serial 收集器是所有垃圾收集器中最古老的的一种。作用于新生代,采用复制算法、单线程、独占的方式进行回收。回收过程中,会暂停所有应用线程(Stop The World)。在 CPU 环境受限(单 CPU)的环境下,有不错的性能,甚至能超过并行/并发收集器。至今仍是虚拟机 Client 模式下的默认垃圾收集器。
启用参数及程序启动 Serial 的工作输出:
-XX:+UseSerialGC
:使用 Serial、Serial Old 分别回收新生代、老年代
1 |
|
ParNew
ParNew 相当于是 Serial 的并行版本,除了使用多线程方式回收垃圾之外,其它都跟 Serial 相同。作用于新生代,采用复制算法,垃圾回收时会暂停应用线程。
启用参数及程序启动 ParNew 的工作输出:
-XX:+UseParNewGC
:使用 ParNew、Serial Old 分别回收新生代、老年代-XX:+UseConcMarkSweepGC
:使用 ParNew、CMS 分别回收新生代、老年代
1 |
|
Parallel Scavenge
Parallel Scavenge 是新生代收集器,采用复制算法,并行方式执行垃圾回收。被称为“吞吐量优先垃圾收集器”,可以控制吞吐量大小(吞吐量 = 运行用户代码时间 /(运行用户代码时间 + 运行垃圾收集时间)[1])。
Parallel Scavenge 收集器有两个可以控制吞吐量的参数和一个自动调节分代大小策略的参数:
-
-XX:MaxGCPauseMills
用户线程暂停(Stop The World)时间阈值,如果指定了该参数,虚拟机将尽量在设定的时间范围内完成垃圾回收。
-
-XX:GCTimeRatio
值域(0, 100),垃圾回收时间跟 JVM 运行总时间占比,也可以理解为 GC 执行频率,公式 1/(1+N)。默认值 99,即有 1/(1+99) 的时间用于垃圾回收。
-
-XX:+UseAdaptiveSizePolicy
开启自动调节策略后,-Xmn(新生代大小),Eden 与 Survivor 的比例(-XX:SurvivorRatio),晋升老年代对象大小(-XX:PretenureSizeThreshold)等参数不需要设置,将由虚拟机动态调节以获得最小延迟或最大吞吐。
启用参数及程序启动 Parallel Scavenge 的工作输出:
-XX:+UseParallelGC
:使用 Parallel Scavenge、Parallel Old 分别回收新生代、老年代-XX:+UseParallelOldGC
:使用 Parallel Scavenge、Parallel Old 分别回收新生代、老年代-XX:-UseParallelOldGC
:使用 Parallel Scavenge、Serial Old 分别回收新生代、老年代
1 |
|
老年代
Serial Old
Serial Old 以串行的方式,采用标记-整理算法,对老年代进行垃圾回收。老年代垃圾回收通常比新生代花费更多时间。因此,在堆内存较大的应用中,Serial Old 启动会导致程序停顿更长时间。
启用参数及程序启动 Serial Old 的工作输出:
-XX:+UseSerialGC
:使用 Serial、Serial Old 分别回收新生代、老年代-XX:+UseParNewGC
:使用 ParNew、Serial Old 分别回收新生代、老年代-XX:+UseParallelGC
:使用 Parallel Scavenge、Serial Old 分别回收新生代、老年代
1 |
|
Parallel Old
Parallel Old 使用标记-整理算法,是一个多线程并发的收集器。是 Parallel Scavenge 的老年代版本,同样关注吞吐。-XX:ParallelGCThreads 参数可以控制垃圾回收时的线程数。
启用参数列表及程序启动 Parallel Old 的工作输出:
-XX:+UseParallelOldGC
:使用 Parallel Scavenge、Parallel Old 分别回收新生代、老年代
1 |
|
CMS
CMS(Concurrent Mark Sweep)是一个尽可能低停顿时间的老年代收集器。垃圾回收分为 4 个主要阶段:
-
初始标记(Initial Mark)
标记 GC Roots 直接关联的对象,暂停应用线程,但速度快。
-
并发标记(Concurrent Mark)
从标记 GC Roots 直接关联的对象开始标记不可用对象,花费较长时间,但可并发执行。
-
重新标记(Remark)
由于并发标记时,应用线程执行产生标记变动,在这个阶段中可以修正。暂停应用线程,比初始标记花费时间长,但相较并发标记,所需的时间短很多。
-
并发清除(Concurrent Sweep)
清除标记的对象,可并发执行。
CMS 的主要设置参数:
-
-XX:ConcGCThreads / -XX:ParallelCMSThreads
CMS 默认启动的回收线程数 (处理器核心数 + 3)/4 ,对于处理器核心数少于 4 个的环境,垃圾回收所占用的处理资源占比较大,容易导致应用程序变慢,降低吞吐。-XX:ConcGCThreads / -XX:ParallelCMSThreads 可以控制 GC 线程数。
-
-XX:CMSInitiatingOccupancyFraction
CMS 无法处理浮动垃圾,可能出现 Concurrent Mode Failure 进而触发一次 Full GC。在并发清除阶段,应用线程可能会再产生垃圾对象,而这一部分对象暂未标记,CMS 暂时无法清理,只能等待下一次垃圾回收。也正是由于应用线程仍然进行中,所以必须预留一部分内存供并发收集时的程序使用。-XX:CMSInitiatingOccupancyFraction(JDK 5 默认 68, JDK 6 默认 92)指当老年代空间使用率达到阈值时,会执行一次 CMS 回收。这个值设定的太低,就会高频率的 CMS 回收;设置过高,则容易导致内存不足,出现 Concurrent Mode Failure 进而临时启用 Serial Old 收集器重新回收老年代。
-
-XX:UseCMSCompactAtFullCollection
CMS 采用标记-清除方法,垃圾回收才产生空间碎片。分配大对象时,如果在空闲记录表无法找到连续内存足够大的空间,则会提前触发 Full GC。-XX:UseCMSCompactAtFullCollection 表示在一次 CMS 回收后,进行碎片整理,碎片清理不是并发执行的。
-
-XX:CMSFullGCsBeforeCompaction
由于 -XX:UseCMSCompactAtFullCollection 的碎片清理会暂停应用线程 (Stop The World),导致延迟变大,影响用户交互。可指定参数 -XX:CMSFullGCsBeforeCompaction,参数表示进行多少次 CMS 回收后,才进行一次碎片整理,可以减少碎片整理次数。
启用参数列表及程序启动 Parallel Old 的工作输出:
-XX:+UseConcMarkSweepGC
:使用 ParNew、CMS 分别回收新生代、老年代
1 |
|
G1
G1(Garbage First) 是一款主要面向服务端应用的垃圾收集器,开创了收集器面向局部收集的设计思路和基于 Region 的内存布局形式。
JDK 6 Update 14,处于”实验状态” (Experimental)。
JDK 7 Update 4,移除 Experimental 标志。
JDK 8 Update 40,提供并发的类卸载的支持,此后的 G1 被 Oracle 官方称为 “全功能的垃圾收集器”。
JDK 9 发布之日,G1 宣布取代 Parallel Scavenge 加 Parallel Old 的组合,称为服务端模式下默认的垃圾收集器,而 CMS 被声明为不推荐使用 Deprecate。
Region 是由连续的堆内存划分出来的多个大小相等的独立区域。根据需要,每个 Region 都可以扮演新生代的 Eden 空间、Survivor 空间、或老年代空间。收集器根据具体的角色,采用不同的策略去处理。Region 还有一类专门用来存储大对象(超过一个 Region 容量一般的对象)的特殊区域 Humongous。
而 G1 基于 Region 组成回收集(Collection Set,一般简称 CSet)进行垃圾回收,衡量标准不再是它属于哪个分代,而是哪块内存的垃圾数量最多,回收收益最大。G1 后台会维护一个优先级列表,记录每块 Region 的回收价值(回收所获得的空间以及回收所需时间的经验值) ,根据用户设定允许的收集停顿时间,优先处理回收价值收益最大的那些 Region。这就是 G1 的 Mixed GC 模式。
G1 大致运作过程分为四个步骤:
-
初始标记(Initial Marking)
标记 GC Roots 直接关联的对象,修改 TAMS 指针的值,暂停应用线程,但速度快。
-
并发标记(Concurrent Marking)
从标记 GC Roots 直接关联的对象开始标记不可用对象,花费较长时间,但可并发执行。扫描完之后,还要重新处理 SATB 记录下,在并发时有引用变动的对象。
-
最终标记(Final Marking)
处理并发阶段结束后仍遗留下来的最后那少量的 SATB 记录,短暂暂停用户线程。
-
帅选回收(Live Data Counting and Evacuation)
更新 Region 的统计数据,对各个 Region 的回收价值和成本进行排序,根据用户期望的停顿时间来制定回收计划,可以自由选择任意多个 Region 构成回收集,然后把决定回收的那一部分 Region 的存活对象复制到空的 Region 中,再清理掉整个旧 Region 的全部空间。这里的操作设计存活对象的移动,是必须暂停用户线程,由多条收集器线程并行完成的。
G1 的主要设置参数:
-
-XX:G1HeapRegionSize
每个 Region 的大小,范围 1MB ~ 32MB,且应为 2 的 N 次幂。
-
-XX:MaxGCPauseMillis
GC 停顿时间,默认 200 毫秒。
总结比较
收集器 | 面向 | 算法 | 运行方式 | 启用 |
---|---|---|---|---|
Serial | 新生代 | 复制 | 串行 | -XX:+UseSerialGC |
Serial Old | 老年代 | 整理 | 串行 | -XX:+UseSerialGC -XX:+UseParNewGC -XX:+UseParallelGC |
ParNew | 新生代 | 复制 | 并行 | -XX:+UseParNewGC -XX:+UseConcMarkSweepGC |
Parallel Scavenge | 新生代 | 复制 | 并行 | -XX:+UseParallelGC -XX:+UseParallelOldGC |
Parallel Old | 老年代 | 整理 | 并发 | -XX:+UseParallelOldGC |
CMS | 老年代 | 清除 | 并发 | -XX:+UseConcMarkSweepGC |
G1 | Region | 并行于并发 | -XX:+UseG1GC |
参考
[1] 周志明. 深入理解Java虚拟机:JVM高级特性与最佳实践(第3版). 3.5 经典垃圾收集器 3.6 低延迟垃圾收集器