转载自 深入JVM虚拟机(四) Java GC收集器
串行收集器主要有两个特点:第一,它仅仅使用单线程进行垃圾回收;第二,它独占式的垃圾回收。
在串行收集器进行垃圾回收时,Java 应用程序中的线程都需要暂停(“StopThe World”),等待垃圾回收的完成,这样给用户体验造成较差效果。虽然如此,串行收集器却是一个成熟、经过长时间生产环境考验的极为高效的收集器。新生代串行处理器使用复制算法,实现相对简单,逻辑处理特别高效,且没有线程切换的开销。在诸如单 CPU 处理器或者较小的应用内存等硬件平台不是特别优越的场合,它的性能表现可以超过并行回收器和并发回收器。在 HotSpot 虚拟机中,使用-XX:+UseSerialGC 参数可以指定使用新生代串行收集器和老年代串行收集器。
- 新生代、老年代使用串行回收。
- 新生代使用复制算法。
- 老年代使用标记-压缩算法。
新生代日志输出:
0.844: [GC 0.844: [DefNew: 17472K->2176K(19648K), 0.0188339secs] 17472K->2375K(63360K), 0.0189186 secs] [Times: user=0.01 sys=0.00,real=0.02 secs]
8.259: [Full GC 8.259: [Tenured: 43711K->40302K(43712K),0.2960477 secs] 63350K->40302K(63360K), [Perm : 17836K->17836K(32768K)],0.2961554 secs]
[Times: user=0.28 sys=0.02, real=0.30 secs]
并行收集器是工作在新生代的垃圾收集器,它只简单地将串行回收器多线程化。它的回收策略、算法以及参数和串行回收器一样。并行回收器也是独占式的回收器,在收集过程中,应用程序会全部暂停。
但由于并行回收器使用多线程进行垃圾回收,因此,在并发能力比较强的 CPU上,它产生的停顿时间要短于串行回收器,而在单 CPU 或者并发能力较弱的系统中,并行回收器的效果不会比串行回收器好,由于多线程的压力,它的实际表现很可能比串行回收器差。开启并行回收器可以使用参数-XX:+UseParNewGC,该参数设置新生代使用并行收集器,老年代使用串行收集器。使用-XX:ParallelGCThreads 限制线程数量。
ParNew并行收集器日志输出:
0.834:[GC 0.834: [ParNew:13184K->1600K(14784K), 0.0092203 secs] 13184K->1921K(63936K), 0.0093401secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Parallel收集器类假于ParNew并行收集器,它使用复制算法的收集器。从表面上看,它和并行收集器一样都是多线程、独占式的收集器。但是,并行回收收集器有一个重要的特点:它非常关注系统的吞吐量。
Parallel收集器可以使用以下参数启用:
-XX:+UseParallelGC:新生代使用并行回收收集器,老年代使用串行收集器。
-XX:+UseParallelOldGC:新生代和老年代都是用并行回收收集器。
这两个参数是矛盾的。因为停顿时间和吞吐量不可能同时调优:
-XX:MaxGCPauseMills:最大停顿时间,单位毫秒。GC尽力保证回收时间不超过设定值。
-XX:GCTimeRatio:垃圾收集时间占总时间的比,0-100的取值范围,默认99,即最大允许1%时间做GC。
Parallel收集器日志输出:
1.500:[Full GC [PSYoungGen:2682K->0K(19136K)] [ParOldGen: 28035K->30437K(43712K)] 30717K->30437K(62848K) [PSPermGen:10943K->10928K(32768K)], 0.2902791 secs] [Times: user=1.44 sys=0.03,real=0.30 secs]
CMS 是 Concurrent Mark Sweep 的缩写,意为并发标记清除,从名称上可以得知,它使用的是标记-清除算法,同时它又是一个使用多线程并发回收的垃圾收集器。与并行回收收集器不同,CMS 收集器主要关注于系统停顿时间。
CMS 工作时,主要步骤有:
初始标记(CMS initial mark): 根可以直接关联到的对象,速度比较快。
并发标记(CMS concurrent mark):主要标记过程,标记全部对象。注:和用户线程一起并发运行。
重新标记(CMS remark): 由于并发标记时,用户线程依然运行,因此在正式清理前,再做修正。
并发清除和并发重置(CMS concurrent sweep): 基于标记结果,直接清理对象。
其中初始标记和重新标记是独占系统资源的,而并发标记、并发清除和并发重置是可以和用户线程一起执行的。因此,从整体上来说,CMS 收集不是独占式的,它可以在应用程序运行过程中进行垃圾回收。
CMS 收集器在其主要的工作阶段虽然没有暴力地彻底暂停应用程序线程,但是由于它和应用程序线程并发执行,相互抢占 CPU,所以在 CMS 执行期内对应用程序吞吐量造成一定影响。CMS 默认启动的线程数是(ParallelGCThreads+3)/4),ParallelGCThreads 是新生代并行收集器的线程数,也可以通过-XX:ParallelCMSThreads 参数手工设定 CMS的线程数量。当 CPU 资源比较紧张时,受到 CMS收集器线程的影响,应用程序的性能在垃圾回收阶段可能会非常糟糕。
标记-清除算法将会造成大量内存碎片,离散的可用空间无法分配较大的对象。在这种情况下,即使堆内存仍然有较大的剩余空间,也可能会被迫进行一次垃圾回收,以换取一块可用的连续内存,这种现象对系统性能是相当不利的,为了解决这个问题,CMS 收集器还提供了几个用于内存压缩整理的算法。
-XX:+UseCMSCompactAtFullCollection参数可以使 CMS 在垃圾收集完成后,进行一次内存碎片整理。内存碎片的整理并不是并发进行的。
-XX:CMSFullGCsBeforeCompaction参数可以用于设定进行多少次 CMS 回收后,进行一次内存压缩。
-XX:ParallelCMSThreads设定CMS的线程数量。
CMS收集器日志输出:
1.662: [GC [1 CMS-initial-mark: 28122K(49152K)] 29959K(63936K), 0.0046877 secs] [Times:user=0.00 sys=0.00, real=0.00 secs]
1.666: [CMS-concurrent-mark-start]
1.699: [CMS-concurrent-mark: 0.033/0.033 secs] [Times: user=0.25 sys=0.00, real=0.03 secs]
1.699: [CMS-concurrent-preclean-start]
1.700: [CMS-concurrent-preclean: 0.000/0.000 secs] [Times:user=0.00 sys=0.00, real=0.00 secs]
1.700: [GC[YG occupancy: 1837 K (14784 K)]1.700: [Rescan(parallel) , 0.0009330 secs]1.701: [weak refs processing, 0.0000180 secs]
[1 CMS-remark:28122K(49152K)] 29959K(63936K), 0.0010248 secs] [Times: user=0.00 sys=0.00,real=0.00 secs]
1.702: [CMS-concurrent-sweep-start]
1.739: [CMS-concurrent-sweep: 0.035/0.037 secs] [Times: user=0.11 sys=0.02, real=0.05 secs]
1.739: [CMS-concurrent-reset-start]
1.741:[CMS-concurrent-reset:0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
CMS特点:
- 尽可能降低停顿
- 会影响系统整体吞吐量和性能
比如,在用户线程运行过程中,分一半CPU去做GC,系统性能在GC阶段,反应速度就下降一半
- 清理不彻底
因为在清理阶段,用户线程还在运行,会产生新的垃圾,无法清理。
因为和用户线程一起运行,不能在空间快满时再清理。
-XX:CMSInitiatingOccupancyFraction设置触发GC的阈值
如果不幸内存预留空间不够,就会引起concurrent mode failure,产生这种问题,通常都使用串行收集器再次垃圾收集,在这种情况下内存基本被占用满了,通常GC停顿的时间较长。
CMS收集器日志输出:
33.348: [Full GC 33.348: [CMS33.357: [CMS-concurrent-sweep:0.035/0.036 secs] [Times: user=0.11 sys=0.03, real=0.03 secs]
(concurrentmode failure): 47066K->39901K(49152K), 0.3896802 secs]60771K->39901K(63936K), [CMS Perm : 22529K->22529K(32768K)], 0.3897989secs] [Times: user=0.39 sys=0.00, real=0.39 secs]
G1收集器与前面的CMS收集器相比有两个显著的改进:一是G1收集器是基于“标记-整理”算法实现的收集器,也就是说它不会产生空间碎片,这对于长时间运行的应用系统来说非常重要。二是它可以非常精确地控制停顿,既能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒,这几乎已经是实时Java(RTSJ)的垃圾收集器的特征了。G1收集器可以实现在基本不牺牲吞吐量的前提下完成低停顿的内存回收,这是由于它能够极力地避免全区域的垃圾收集,之前的收集器进行收集的范围都是整个新生代或老年代,而G1将整个Java堆(包括新生代、老年代)划分为多个大小固定的独立区域(Region),并且跟踪这些区域里面的垃圾堆积程度,在后台维护一个优先列表,每次根据允许的收集时间,优先回收垃圾最多的区域(这就是Garbage First名称的来由)。区域划分及有优先级的区域回收,保证了G1收集器在有限的时间内可以获得最高的收集效率。
1、 Serial串行收集器相关的参数
-XX:+UseSerialGC |
新生代和老年代使用串行回收器。 |
-XX:SurvivorRatio |
设置eden区大小和survivior区大小的比例。 |
-XX:PretenureSizeThreshold |
设置大对象直接进入老年代的阈值。当对象的大小超过这个值时,将直接在老年代分配。 |
-XX:MaxTenureSizeThreshold |
设置对象进入老年代的年龄的最大值。每一次Minor |
2、 ParNew并行收集器相关的参数
-XX:+UseParNewGC |
在新生代使用并行收集器。 |
-XX:+UseParallelGC |
新生代使用并行回收收集器 |
-XX:+UseParallelOldGC |
老年代使用并行回收器。 |
-XX:ParallelGCThreads |
设置用于垃圾回收的线程数。通常情况下可以和CPU数量相等,但在CPU数量比较多的情况下,设置相对较小的数值也是合理的。 |
-XX:MaxGCPauseMills |
设置最大垃圾收集停顿时间。它的值是一个大于0的正数。收集器在工作时,会调整java堆大小或者其他一些参数,尽可能地把停顿时间控制在MaxGCPauseMills以内。 |
-XX:GCTimeRatio |
设置吞吐量大小。它的值时一个0到100之间的整数。假设GCTimeRatio的值为n,那么系统将花费不超过1/(1+n)的时间用于垃圾收集。 |
-XX:+UseAdaptiveSizePolicy |
打开自适应GC策略。在这种模式下,新生代的大小、eden和survivior的比例、晋升老年代的对象年龄等参数会被自动调整,以达到在堆大小、吞吐量和停顿时间之间的平衡点。 |
3、 CMS 收集器相关的参数
-XX:+UseConcMarkSweepGC |
新生代使用并行收集器,老年代使用CMS+串行收集器。 |
-XX:ParallelCMSThreads |
设定CMS的线程数量。 |
-XX:CMSInitiatingOccupancyFraction |
设置CMS收集器在老年代空间被使用多少后触发,默认为68%。 |
-XX:+UseCMSCompactAtFullCollection |
设置CMS收集器在完成垃圾收集后是否要进行一次内存碎片整理。 |
-XX:CMSFullGCsBeforeCompaction |
设定进行多少次CMS垃圾回收后,进行一次内存压缩。 |
-XX:+CMSClassUnloadingEnabled |
允许对类元数据区进行回收。 |
-XX:CMSInitiatingPermOccupancyFraction |
当永久区占用率达到这一百分比时,启动CMS回收(前提是-XX:+CMSClassUnloadingEnabled激活了)。 |
-XX:CMSInitiatingPermOccupancyOnly |
表示只在到达阈值的时候才进行CMS回收。 |
-XX:+CMSIncrementalMode |
使用增量模式,比较适合单CPU。增量模式在JDK8中标记为废弃,并且将在JDK9中彻底移除。 |
4、 G1收集器相关的参数
-XX:+UseG1GC |
使用G1回收器。 |
-XX:MaxGCPauseMillis |
设置最大垃圾收集停顿时间。 |
-XX:GCPauseIntervalMillis |
设置停顿间隔时间。 |