一.前言
前文学习了各种GC回收算法,掌握了GC回收的原理,但是真正的GC实现却尤为复杂,本篇文章将主要介绍各种GC收集器。
目前主流的HotSpot VM支持多种虚拟机,这些虚拟机也体现了GC的发展历程,从单线程GC到多线程GC,分代GC到G1 GC。
本文主要从以下几个方面介绍GC收集器:
- 各种GC的特点
- GC匹配和参数使用
- GC日志格式
- 常用的GC参数总结
### 二.各种GC的特点
HotSpot中采用分代GC,从早期的单线程串行Garbage Collector到后面的多线程并行Garbage Collecot,衍生出了很多款Collector。
其中负责收集年轻代:
- Serial:单线程串行收集器,使用复制算法回收年轻代
- ParNew:多线程并行收集器,使用复制算法回收年轻代
- Parallel Scavenge:多线程并行收集器,使用复制算法回收年轻代
其中负责收集老年代:
- Serial Old:类似Serial,单线程串行收集器,使用标记整理算法回收老年代
- CMS:并发标记整理的收集器,使用标记清除算法回收老年代
- Parallel Old:类似ParNew和Parallel Scavenge,使用标记整理算法回收老年代
1.Serial收集器
Serial收集器是一款非常古老的收集器,它使用单线程串行方式回收年轻代,会产生STW。
Note:
STW即Stop The World,即停止所有用户线程,只有GC线程在运行。
每次进行GC时,首先停顿所有的用户线程,然后只有一个GC线程回收年轻代中的死亡对象。在Java Client模式中,默认任然使用Serial,因为Client模式主要针对桌面应用,一般内存较小,在百M范围内,使用单线程收集Serial效率非常高,可以带来很少时间的停顿,用户体检非常好。
2.ParNew收集器
在早期,只有单线程收集器时,年轻代别无选择。后续又演变成多线程GC年轻代,便衍生出ParNew这款并行收集器,它的并行实现主要是在GC期间使用多线程回收年轻代。
这款并行收集器在GC期间,也需要STW。一般多数用于Server端的年轻代GC。
3.Parallel Scavenge收集器
顾名思义,这个款年轻代收集器也是并行收集器,和ParNew的功能差不多,同样适用复制算法。但是它更注重系统运行的吞吐量。这里说的吞吐量,指的是CPU用于运行应用程序的时间和CPU总时间的占比,吞吐量 = 代码运行时间 / (代码运行时间 + 垃圾收集时间)。但是它的来源比较奇葩,没有遵循GC框架,导致和CMS不能兼容。关于这点可以参考:
ParNew 和 PSYoungGen 和 DefNew 是一个东西么?
4.Serial Old收集器
该收集器和Serial收集器的功能一样,都是单线程串行收集器,GC期间也会STW。但是它用于收集老年代且使用了标记整理算法,这两点它和Serial收集器不一样。主要也是应用在Client模式下的桌面应用中。
5.Parallel Old收集器
Parallel Old和ParNew和Parallel Scavenge类似,是一款老年代的多线程并行收集器。一般只配合Parallel Scavenge使用。
6.CMS收集器
CMS(Concurrent Mark Sweep 并发标记清理)收集器是日常应用中最常被使用的收集器。它主要是为了减少停顿的时间,降低延迟而生,多应用对实时性要求比较的应用场景,如:互联网应用。
它主要分为四个过程:
- 初始标记:这阶段将标记不可达对象,标记阶段将暂停所有用户线程,造成STW。但是这阶段只是标记出GC Roots,停顿时间相对较短。
- 并发标记:这阶段GC线程将会和用户线程同时运行,将从初始阶段标记出的GC Roots出发标记老年代所有对象
- 重新标记:这阶段将暂停所用用户线程,造成STW。但是同样相对较短,主要是为了重新标记出在并发阶段发生引用变化的对象,因为并发标记阶段是和用户线程并发运行,可能会造成对象的引用关系发生变化。
- 并发清除:这是最后一个阶段,也是和用户线程同时运行的。将并发的清理掉被标记的死亡对象。
其中初始标记和重新标记仍然会STW暂停用户线程,但是这两个过程的停顿时间相对于并发标记和并发清除而言相对较短,而并发标记和并发清除阶段GC线程则可以和用户线程并发运行。
由于CMS收集器同样使用标记清除算法,所以存在内存碎片问题,从而可能造成大对象无法分配发生提前GC。所以CMS收集器又提供了参数控制其进行内存碎片整理,默认是开启状态,这个过程是非常长的。
三.GC匹配和参数使用
从以上内容介绍,可以看出分代GC分为很多种,随着演化过程,每种都有各自的应用场景。从其收集特点上可以分为三类:
- 单线程串行收集
- 多线程并发串行收集
- 多阶段并行收集
虽然这些分代收集器种类繁多,但是他们之间有相互匹配,并非任意使用。配对的使用情况以及参数见下表:
young | old | 参数 |
---|---|---|
Serial | Serial old | -XX:+UseSerialGC |
ParNew | Serial old | -XX:+UseParNewGC |
Parallel Scavenge | Serial old | -XX:+UseParallelGC |
Parallel Scavenge | Parallel Old | -XX:+UseParallelOldGC |
ParNew | CMS + Serial Old | -XX:+UseConcMarkSweepGC |
Serial | CMS | -XX:+UseConcMarkSweepGC -XX:-UseParNewGC |
### 四.GC日志格式
GC收集器众多,每种GC收集器的GC日志格式都不一样。这里笔者做了下总结,同样是针对以上的各种匹配情况做了日志格式的收集。
1.Serial + Serial old格式
0.299: [GC (Allocation Failure) 0.299: [DefNew: 1770K->428K(4928K), 0.0019955 secs] 9962K->8620K(15872K), 0.0020405 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.304: [GC (Allocation Failure) 0.304: [DefNew (promotion failed) : 4612K->4193K(4928K), 0.0014249 secs]0.305: [Tenured: 8588K->4499K(10944K), 0.0034912 secs] 12804K->4499K(15872K), [Metaspace: 3094K->3094K(1056768K)], 0.0049774 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
使用Serial + Serial Old搭配式,年轻代收集将会是DefNew,老年代将会是Tenured。
1. ParNew + Serial old格式
0.243: [GC (Allocation Failure) 0.243: [ParNew: 1770K->470K(4928K), 0.0029402 secs] 9962K->8662K(15872K), 0.0029933 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
0.251: [GC (Allocation Failure) 0.251: [ParNew (promotion failed): 4566K->4096K(4928K), 0.0009985 secs]0.252: [Tenured: 8632K->4516K(10944K), 0.0025514 secs] 12758K->4516K(15872K), [Metaspace: 3093K->3093K(1056768K)], 0.0035906 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
使用Serial + Serial Old搭配式,年轻代收集将会是ParNew,老年代将会是Tenured。
3.Parallel Scavenge + Parallel Old格式
0.224: [GC (Allocation Failure) [PSYoungGen: 0K->0K(4608K)] 8619K->8619K(15872K), 0.0004876 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.225: [Full GC (Allocation Failure) [PSYoungGen: 0K->0K(4608K)] [ParOldGen: 8619K->8601K(11264K)] 8619K->8601K(15872K), [Metaspace: 3080K->3080K(1056768K)], 0.0046498 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
使用Parallel Scavenge + Parallel Old搭配式,年轻代收集将会是PSYoungGen,老年代将会是ParOldGen。
4.ParNew + CMS格式
0.351: [GC (Allocation Failure) 0.351: [ParNew: 1769K->446K(4928K), 0.0008713 secs] 9961K->8638K(15872K), 0.0009216 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
0.354: [GC (CMS Initial Mark) [1 CMS-initial-mark: 8192K(10944K)] 12734K(15872K), 0.0004513 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.355: [CMS-concurrent-mark-start]
0.355: [CMS-concurrent-mark: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.355: [GC (Allocation Failure) 0.355: [ParNew (promotion failed): 4631K->4267K(4928K), 0.0007242 secs]0.356: [CMS (concurrent mode failure): 8207K->4500K(10944K), 0.0031171 secs] 12823K->4500K(15872K), [Metaspace: 3080K->3080K(1056768K)], 0.0038915 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
使用CMS收集器的GC日志格式非常明显,有CMS的GC过程,年轻代将使用ParNew标志。
### 五.常用的GC参数总结
1.开启GC日志
- -verbose:gc:打印GC日志
- -XX:+PrintGCDateStamps:打印GC日志时间戳
- -XX:PrintGCDetails:打印GC日志详情
- -XX:+PrintGCTimeStamps:打印此次GC距离JVM开始运行的时间
- -XX:+PrintGCApplicationStopedTime:打印GC造成的应用暂停时间
- -XX:+PrintTenuringDistribution:打印对象晋升日志
2.通用参数
- -XX:+HeapDumpOnOutOfMemoryError:内存溢出时,产生heap dump文件
- -Xloggc::将GC日志输出到指定文件
- -XX:-+DisableExplicitGC:禁用System.gc(),该方法默认会触发FGC
- -XX:MaxTenuringThreshold: 新生代 to 区的对象在经过多次 GC 后,如果还没有死亡,则认为他是一个老对象,则可以晋升到老年代,默认是15。但该参数不是唯一决定对象晋升的条件,当 to区不够或者该对象年龄已经达到了平均晋升值或者大对象等等条件
- -XX:TargetSurvivorRatio 决定对何时晋升的不仅只有 XX:MaxTenuringThreshold 参数,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半(默认50%)
- -XX:+UseTLAB:启用线程本地分配缓存,默认开启
- -XX:+PrintTLAB:打印TLAB的使用情况