《实战Java虚拟机:JVM故障诊断与性能优化 (第2版)》
Java 平台,标准版热点虚拟机垃圾回收调优指南
垃圾收集器虽然看起来数量比较多,但其实总体逻辑都是因为硬件环境的升级而演化出来的产品,不同垃圾收集器的产生总体可以划分为几个阶段。
- 单线程收集时代(Serial 和 Serial Old)
- 多线程收集时代(Parallel Scanvenge 和 Parallel Old)
- 并发收集时代(ParNew 和 CMS)
- 智能并发收集时代(G1)
垃圾回收分为分代回收和分区回收:
- 分代回收:Serial、Serial Old、Parallel Scavenge、Parallel Old、ParNew、CMS
- 分区回收:G1、ZGC、Shenandoah
第4章 垃圾收集器和内存分配
5.1. 串行回收器(一心一意一件事)
1.使用单线程进行垃圾回收,每次回收时,只有一个工作线程;是一个成熟且经过长时间生产环境考验的极为高效的收集器;
2.特点:专注性、独占性
3.适用场景:并行能力较弱的计算机;
4.可使用场景:新生代和老年代;
5.分类:
根据作用于不同的堆空间,分为新生代串行回收器和老年代串行回收器。
5.1.1. 新生代串行回收器 - Serial
1.最古老,也是 JDK 中最基本的垃圾回收器之一;
2.特点:
- 使用单线程进行垃圾回收;
- 独占式的垃圾回收;
3.采用复制算法;实现相对简单、逻辑处理特别高效且没有线程切换的开销;
4.在单 CPU 处理器等硬件平台不是特别优越的情况下,性能表现可以超过并行回收器和并发回收器;
5.使用 -XX:+UseSerialGC
参数可以指定使用新生代串行回收器或老年代串行回收器;JVM 在 Client 模式下运行时,是默认的垃圾回收器;
6.注意:串行垃圾回收器虽然古老,但是久经考验。在大多数情况下,其性能表现是相当不错的;
在串行收集器进行垃圾回收时,Java 应用程序中的线程都需要暂停,等待垃圾回收的完成。如图5.1所示,在串行回收器运行时,应用程序中的所有线程都停止工作,进行等待。这种现象称之为 “Stop-The-World” 。它将造成非常糟糕的用户体验,在实时性要求较高的应用场景中,这种现象往往时不能被接受的。
5.1.2. 老年代串行回收器 - Serial Old
1.采用标记压缩算法;
2.和 Serial 回收器一样,是串行、独占式的垃圾回收器;
3.若要启用老年代串行回收器,可以尝试使用以下参数:
-XX:+UseSerialGC
:新生代、老年代都使用串行回收器;
-XX:+UseParNewGC
:新生代使用 ParNew 回收器,老年代使用串行收集器;(JDK9、JDK10 已经删除,因为 ParNew 需要和 CMS 搭配工作,而 CMS 已经被 G1 替代,不再支持此参数)
-XX:+UseParallelGC
:新生代使用 ParallelGC 回收器,老年代使用串行回收器;
由于老年代垃圾回收通常会使用比新生代回收更长的时间,因此,在堆空间较大的应用程序中,一旦老年代串行收集器启动,应用程序很可能会因此停顿较长的时间。
虽然如此,作为老牌的垃圾回收器,老年代串行回收器可以和多种新生代回收器配合使用,同时它也可以作为 CMS 回收器的备用回收器。
5.2. 并行回收器(人多力量大)
1.并行回收器在串行回收器的基础上做了改进,使用多个线程同时进行垃圾回收;
2.对于并行能力强的计算机,可以有效缩短垃圾回收所需的实际时间;
5.2.1. 新生代回收器 - ParNew
1.工作在新生代;
2.只是简单地将串行回收器多线程化,它的回收策略、算法以及参数和新生代串行回收器一样;
3.独占式回收器,在收集过程中,应用程序会全部暂停;
并行回收器使用多线程进行垃圾回收,因此,在并发能力比较强的 CPU 上,它产生的停顿时间要短于串行回收器,而在但 CPU 或者并发能力较弱的系统中,并行回收器的效果不会比串行回收器好,由于多线程的压力,它的实际表现很可能比串行回收器差;
4.开启 ParNew 回收器可以使用参数:
-XX:+UseParNewGC
:新生代使用 ParNew 回收器,老年代使用串行回收器;(JDK9、JDK10 已经删除,因为 ParNew 需要和 CMS 搭配工作,而 CMS 已经被 G1 替代,不再支持此参数)
-XX:+UseConcMarkSweepGC
:新生代使用 ParNew 回收器,老年代使用 CMS;(JDK9、JDK10 不建议使用,建议使用默认的 G1 垃圾回收器)
5.可以使用-XX:ParallelGCThreads
参数指定工作时的线程数量。一般,最好与 CPU 数量相当,避免过多的线程数,影响垃圾收集性能。在默认情况下,当 CPU 数量小于 8 个时,ParallelGCThreads 的值等于 CPU 数量,当 CPU 数量大于 8 个时,ParallelGCThreads 的值等于 3+((5*CPU_Count)/8);
5.2.2. 新生代回收器 - ParallelGC
1.采用复制算法;
2.和 ParNew 回收器一样,多线程、独占式;
3.特点:非常关注系统的吞吐量;
4.新生代回收器 ParallelGC 回收器可以使用以下参数启用:
-XX:+UseParallelGC
:新生代使用 ParallelGC 回收器,老年代使用串行回收器;
-XX:+UseParallelOldGC
:新生代使用 ParallelGC 回收器,老年代使用 ParallelOldGC 回收器;
5.ParallelGC 回收器提供了两个重要的参数用于控制系统的吞吐量:
-XX:MaxGCPauseMillis
:设置最大垃圾回收停顿时间,值是 > 0 的整数;Parallel 在工作时,会调整 Java 堆大小或者其他参数,尽可能地把停顿时间控制在 MaxGCPauseMillis 以内。该值小将导致垃圾回收变得很频繁,从而增加垃圾回收总时间,降低吞吐量;
-XX:GCTimeRatio
:设置吞吐量大小,值是 0 - 100 之间的整数;假设 GCTimeRatio 的值为 n,系统将花费不超过 1/(1+n) 的时间进行垃圾回收;默认情况下,取值为 99,即有不超过 1/(1+99) = 1% 的时间用于垃圾回收;
ParallelGC 与 ParNew 另一个不同之处在于,它还支持一种自适应的 GC 调节策略;使用
-XX:+UseAdaptiveSizePolicy
可以打开自适应 GC 策略;在这种模式下,新生代的大小、eden 区和 survivor 区的比例、晋升老年代的对象年龄等参数会被自动调整,以达到在堆大小、吞吐量和停顿时间之间的平衡点。在手工调优比较困难的场合,可以直接使用这种自适应的方式,仅指定 JVM 的最大堆、目标吞吐量(GCTimeRatio)和停顿时间(MaxGCPauseMillis),让 JVM 自己完成调优工作。
6.-XX:MaxGCPauseMillis
和-XX:GCTimeRatio
参数是相互矛盾的,如果减少一次收集的最大停顿时间,就会同时减少系统吞吐量,增加系统吞吐量又可能会同时增加一次垃圾回收的最大停顿时间;
5.2.3. 老年代回收器 - ParallelOldGC
1.多线程并发;
2.和新生代回收器 ParallelGC 一样,也是关注吞吐量的回收器;
3.老年代回收器,和 ParallelGC 新生代回收器搭配使用;
4.采用标记压缩法;在 JDK1.6 中才可以使用;
5.参数:
-XX:+UseParallelOldGC
:在新生代使用 ParallelGC 回收器,老年代使用 ParallelOldGC 回收器;(这是一对非常关注吞吐量的垃圾回收器,在对吞吐量敏感的系统中可以考虑使用)
-XX:ParallelGCThreads
:可以用于设置垃圾回收时的线程数量;
5.3. CMS 回收器(一心多用都不落下)(JDK8及之前的版本)
1.关注系统停顿时间。
2.CMS 是 Concurrent Mark Sweep 的缩写,意为并发标记清除;
3.采用标记清除法;
4.多线程并行回收;
5.3.1. CMS 主要工作步骤
1.CMS 工作时的主要步骤有:初始标记、并发标记、预清理、重新标记、并发清除和并发重置;
2.初始标记和重新标记是独占系统资源的;预清理、并发标记、并发清除和并发重置是可以和用户线程一起执行的。
3.从整体上说,CMS 不是独占的,它可以在应用程序运行过程中进行垃圾回收。
根据标记清除法,初始标记、并发标记和重新标记都是为了标记出需要回收的对象。 并发清理则是在标记完成后,正式回收垃圾对象。并发重置是指在垃圾回收完成后,重新 初始化CMS数据结构和数据,为下一次垃圾回收做好准备。并发标记、并发清理和并发重置都是可以和应用程序线程一起执行的。
在整个CMS的回收过程中,默认情况下,在并发标记之后,会有一个预清理的操作 (也可以关闭开关-XX:-CMSPrecleaningEnabled
,不进行预清理)。预清理是并发的,除了为正式清理做准备和检查,还会尝试控制一次停顿的时间。由于重新标记是独占 CPU 的,如果新生代 GC 发生后,立即触发一次重新标记,那么一次停顿的时间可能很长。为了避免这种情况,预处理时会刻意等待一次新生代GC的发生,然后根据历史性能数据预测下一次新生代GC可能发生的时间,在当前时间和预测时间的中间时刻进行重新标记。 这样可尽量避免新生代GC和重新标记重合,尽可能减少一次停顿的时间。
5.3.2. CMS 主要的参数
1.-XX:+UseConcMarkSweepGC
:启用 CMS 回收器;
2.-XX:ParallelGCThreads
:设置 GC 并行时使用的线程数量,如果新生代使用 ParNew,则 ParallelGCThreads 也是新生代 GC 的线程数量;默认启动的并发线程数是 (ParallelGCThreads+3)/4;这意味着有 4 个 ParallelGCThreads 时,只有 1 个并发线程,而有两个并发线程时,有 5-8 个 ParallelGCThreads 线程;
3.-XX:ConcGCThreads
、-XX:ParallelCMSThreads
:设置并发线程数量;
4.注意:
(1)并发:指收集器和应用线程交替执行;
(2)并行:指应用程序停止,同时由多个线程一起执行GC ;(并行回收器不是并发的)
由于CMS回收器不是独占式的回收器,在CMS回收过程中,应用程序仍然在不停地 工作。在应用程序工作过程中,又会不断地产生垃圾。这些新生成的垃圾在当前CMS回 收过程中是无法清除的。同时,因为应用程序没有中断,所以在CMS回收过程中,还应 该确保应用程序有足够的内存可用。因此,CMS回收器不会等待堆内存饱和时才进行垃 圾回收,而是当堆内存使用率达到某一阈值时便开始进行回收,以确保应用程序在CMS 工作过程中,依然有足够的空间支持应用程序运行。
5.-XX:CMSInitiatingOccupancyFraction
:指定回收阈值,默认是 68,即当老年代的空间使用率达到 68% 时,会执行一次 CMS 回收;
如果应用程序的内存使用率增长很快,在 CMS 的执行过程中,已经出现了内存不足的情况,CMS 回收就会失败,虚拟机将启动老年代串行回收器进行垃圾回收。此时,应用程序将完全中断,直到垃圾回收完成,这时,应用程序的停顿时间可能会较长。
根据应用程序的特点,可以对参数
-XX:CMSInitiatingOccupancyFraction
进行调优。如 果内存增长缓慢,则可以设置一个稍大的值,大的阈值可以有效降低CMS的触发频率, 减少老年代回收的次数,可以较为明显地改善应用程序性能。反之,如果应用程序内存使 用率增长很快,则应该降低这个阈值,以避免频繁触发老年代串行回收器。
6.CMS 基于标记清除法,将会产生大量内存碎片,离散的可用空间无法分配给较大的对象。如下图 5.5,在这种情况下,即使堆内存仍然有较大的剩余空间,也可能会被迫进行一次垃圾回收,以换取一块可用的连续内存。这种现象对系统性能是相当不利的,为了解决这个问题,CMS 回收器提供了几个用于内存压缩整理的参数:
-XX:+UseCMSCompactAtFullCollection
:可以使 CMS 在垃圾收集完成后,进行一次内存碎片整理,内存碎片的整理不是并发进行的;
-XX:CMSFullGCsBeforeCompaction
:可以用于设定进行多少次 CMS 回收后,进行一次内存压缩;
5.3.3. CMS 的日志分析
这说明CMS回收器并发回收失败。这很可能是应用程序在运行过程中老年代空间不 够导致的。如果在CMS工作过程中,出现非常频繁的并发模式失败,就应该考虑进行调 整,尽可能预留一个较大的老年代空间。或者可以设置一个较小的- XX:CMSInitiatingOccupancyFraction参数,降低CMS触发的阈值,使CMS在执行过程中仍然有较大的老年代空闲空间供应用程序使用。
5.3.4. 有关 Class 的回收
在使用 CMS 回收器时,如果需要回收 Perm 区,那么默认情况下,还需要触发一次 Full GC,如下所示:
-XX:+CMSClassUnloadingEnabled
:使用 CMS 回收器回收 Perm 区,使用该参数后,如果条件允许,系统会使用 CMS 的机制回收 Perm 区的 Class 数据,日志如下:
5.4. G1 回收器(未来我做主)(JDK9 及之后版本的默认回收器)
1.G1 回收器(Garbage-First)是在 JDK1.7 中正式使用的全新的垃圾回收器,为了取代 CMS 回收器。
2.G1 有独特的垃圾回收策略,和之前的回收器截然不同;
3.属于分代垃圾回收器,会区分年轻代和老年代,依然有 eden 区和 survivor 区;
4.使用了分区算法;它不要求整个 eden 区、年轻代或者老年代都连续;
5.特点:
(1)并行性:在回收期间,多个 GC 线程同时工作;(有效利用多核计算能力)
(2)并发性:拥有与应用程序交替执行的能力,部分工作可用和应用程序同时执行,一般来说,不会在整个回收期间完全阻塞应用程序;
(3)分代 GC:同时兼顾年轻代和老年代;(其他回收器或者工作在年轻代,或者工作在老年代);
(4)空间整理:在回收过程中,会进行适当的对象移动,每次回收都会有效地复制对象,减少碎片空间;不像 CMS,只是简单地标记清理对象,在若干次 GC 后,CMS 必须进行一次碎片整理;
(5)可预见性:由于分区原因,G1 可以只选取部分区域进行内存回收,缩小了回收的范围,全局停顿也能得到较好的控制;
5.4.1. G1 的内存划分和主要收集过程
1.G1 将堆进行分区,划分为一个个的区域,每次回收的时候,只回收其中几个区域,以此来控制垃圾回收产生的一次停顿的时间;
2.回收过程可能有 4 个阶段:
(1)新生代 GC;
(2)并发标记周期;
(3)混合回收;
(4)如果需要,可能会进行 Full GC;
5.4.2. G1 的新生代 GC
1.主要工作:回收 eden 区和 survivor 区;
2.回收后:eden 区被清空;survivor 区回收一部分,至少存在一个 survivor 区;老年代区域增多(部分 survivor 区或者 eden 区对象晋升);
新生代 GC 发生后,如果打开了 PrintGCDetails 选项,就可以得到类似如下的 GC 日志:
和其他回收器的日志相比,G1 的日志内容非常丰富。当然我们最关心的依然是 GC 的 停顿时间及回收情况。从日志中可以看到,eden 区原本占用 235MB 空间,回收后被清空, survivor 区从 5MB 增长到了11MB,这是因为部分对象被从 eden 区复制到 survivor 区,整个 堆合计为 400 MB,堆内存从回收前的239MB 下降到 10.5MB。
5.4.3. G1 的并发标记周期
1.并发阶段类似 CMS,为了降低一次停顿时间,将可以和应用程序并发的部分单独提取出来执行;
2.并发标记周期分步:
- 初始标记:标记从根节点直接可达的对象。这个阶段会伴随一次新生代 GC,它是会产生全局停顿的,引用程序线程在这个阶段必须停止执行;
- 根区域扫描:扫描由 survivor 区直接可达的老年代区域,并标记这些直接可达的对象。此过程可以应用程序并发执行,但不能和新生代 GC 同时执行(因为根区域扫描依赖 survivor 区的对象,而新生代 GC 会修改这个区域),如果恰巧需要进行新生代 GC,就需要等待根区域扫描结束后才能进行,在这种情况下,这次新生代 GC 的时间就会延长;
- 并发标记:和 CMS 类似,并发标记会扫描并查找整个堆的存活对象,并做好标记;这是一个并发的过程,可以被一次新生代 GC 打断;
- 重新标记:和 CMS 一样,重新标记也会产生应用程序停顿;由于在并发标记过程中,应用程序依然在运行,因此标记结果可能需要进行修正,所以在此对上一次的标记结果进行补充;在 G1 中,这个过程使用 STAB(Snapshot-At-The-Beginning)算法完成,即 G1 会在标记之初为存活对象创建一个快照,这个快照有助于加速重新标记的速度;
- 独占清理:这个阶段是会引起停顿的。它将计算各个区域的存活对象和 GC 回收比例,并进行排序,识别可供混合回收的区域;在这个阶段,还会更新记忆集(Remebered Set);该阶段给出了需要被混合回收的区域并进行了标记,在混合回收阶段需要这些信息;
- 并发清理:并发清理,不会引起停顿;会识别并清理完全空闲的区域;
图 5.7 显示了并发标记周期前后堆的可能情况。由于并发标记周期包含一次新生代 GC,故新生会被整理,但由于并发标记周期执行时,应用程序依然在运行,所以并发标记周期结束后,又会有新的 eden 区的空间被使用。并发标记周期执行前后最大的不同是在该阶段后,系统增加了一些标记为 G 的区域。这些区域被标记,是因为它们内部的垃圾比例较高,希望在后续的混合 GC 中进行收集(注意在并发标记周期中并未正式收集这些区域)。这些将要被回收的区域会被 G1 记录在一个称为 Collection Sets(回收集)的集合中。
并发回收阶段的整体工作流程如图 5.8 所示,可以看到除了初始标记、重新标记和独占清理,其他几个阶段都可以和应用程序并发执行;
在并发标记周期中,G1 会产生如下日志:
(1)初始标记,它伴随一次新生代 GC,可以看到,初始化标记时,eden 区被清空,并部分复制到 survivor 区;
(2)一次并发的根区域扫描,并发扫描过程不能被新生代 GC 中断;根区域扫描不会产生停顿;
(3)并发标记,并发标记可以被新生代 GC 打断,下面的日志显示了一次并发标记被 3 次新生代 GC 打断;
(4)重新标记,是会引起全局停顿的,它的日志如下:
(5)重新标记后会进行独占清理,独占清理会重新计算各个区域的存活对象,并以此可以得到每个区域进行 GC 的效果(即回收比)。它的日志如下:
(6)并发清理,是并发执行的,它会根据独占清理阶段计算得出的每个区域的存活对象数量,直接回收已经不包含存活对象的区域。它的日志如下:
5.4.4. 混合回收
在并发标记周期中,虽然有部分对象被回收,但是总体上说,回收的比例是相当低的。但是在并发标记周期后,G1 已经明确知道哪些区域含有比较多的垃圾对象,在混合回收阶段就可以专门针对这些区域进行回收。当然,G1 会优先回收垃圾比例较高的区域,因为回收这些区域的性价比也比较高。这也正是 G1 名字的由来。G1 垃圾回收器的全称为 Garbage First Garbage Collector,直译为垃圾优先的垃圾回收器,这里的垃圾优先 (Garbage First)指的就是回收时优先选取垃圾比例最高的区域。
这个阶段叫作混合回收,是因为在这个阶段既会执行正常的年轻代GC,又会选取一 些被标记的老年代区域进行回收,它同时处理了新生代和老年代,如图 5.9 所示。因为新 生代 GC 的原因,eden 区必然被清空,此外,有两块被标记为 G 的垃圾比例最高的区域被清理。被清理区域中的存活对象会被移到其他区域,这样做的好处是可以减少空间碎片。
混合 GC 会执行多次,直到回收了足够多的内存空间,然后它会触发一次新生代 GC。 新生代 GC 后,又可能会发生一次并发标记周期的处理,最后又会引起混合GC的执行。整 个过程可能如图 5.10 所示。
5.4.5. 必要时的 Full GC
1.和 CMS 类似,并发回收由于让应用程序和 GC 线程交替工作,总是不能完全避免在特别繁忙的场合出现在回收过程中内存不充足的情况。当遇到这种情况时,G1 也会转入一个 Full GC;
2.当 G1 在并发标记时,由于老年代被快速填充,G1 会终止并发标记而转入一个 Full GC;
3.如果在混合 GC 时空间不足,或者在新生代 GC 时,survivor 区和老年代无法容纳幸存对象,都会导致一次 Full GC;
5.4.6. G1 日志
一个完整的 G1 新生代日志如下:
(1)日志第一行,表示在应用程序开启 1.619 秒时发生了一次新生代 GC,这是在初始标记时发生的,耗时 0.038 秒,意味着应用程序至少暂停了 0.038 秒;
(2)第二行给出后续并行时间。表示所有 GC 线程总的花费时间,这里为 38 毫秒;
(3)第三四行给出每一个 GC 线程的执行情况。这里表示一共 4 个GC 线程(因为第一行有 4 个数据),它们都在 1619.3 秒时启动。同时,还给出了这几个启动数据的统计值,如平均(Avg)、最小(Min)、最大(Max)和差值(Diff)。Diff 表示最大值和最小值的差;
(4)第五六行给出了根扫描的耗时。在根扫描时,每一个 GC 线程的耗时,这里分别消耗了 0.3、0.3、0.2、0.2 秒,后一行给出了这些耗时的统计数据;
(5)第七八九十行给出了更新记忆集(Remembered Set)的耗时。记忆集是 G1 中维护的一个数据结构,简称 RS。每一个 G1 区域都有一个 RS 与之关联。由于 G1 回收时是按照区域回收的,比如在回收区域 A 的对象时,很可能并不回收区域 B 的对象,为了回收区域 A 的对象,要扫描区域 B 甚至整个堆来判断区域 A 中哪些对象不可达,这样做的代价显然很大。因此,G1 在区域 A 的 RS 中,记录了在区域 A 中被其他区域引用的对象,这样在回收区域 A 时,只要将 RS 视为区域 A 根集的一部分即可,从而避免做整个堆的扫描。由于系统在运行过程中,对象之间的引用关系时可能时刻变换的,为了更高效地跟踪这些引用关系,会将这些变化记录在 Update Buffers 中。这里的 Processed Buffers 指的就是处理这个 Update Buffers 数据。这里给出的 4 个时间也是 4 个 GC 线程的耗时,以及它们的统计数据。从这个日志中可以看到,更新 RS 时分别耗时 5.7、5.4、28、5.3 毫秒,平均耗时 11.1 毫秒;
(6)第十一、十二行表示扫描 RS 的时间;
(7)在正式回收时, G1 会对被回收区域的对象进行疏散,即将存活对象放置在其他区域中,因此需要进行对象的复制,第十三、十四行给出的 Object Copy 就是进行对象赋值的耗时;
(8)第十五、十六、十七、十八行给出 GC 工作线程的终止信息。这里的终止时间是线程花在终止阶段的耗时。在 GC 线程终止前,它们会检查其他 GC 线程的工作队列,查看是否仍然还有对象引用没有处理完,如果其他线程仍然有没有处理完的数据,请求终止的 GC 线程就会帮助它尽快完成,随后再尝试终止。其中 Termination Attempts 展示了每一个工作线程尝试终止的次数;
(9)第十九、二十行显示 GC 工作线程的完成时间,这里显示了在系统运行后 1657 毫秒,这几个线程都终止了;
(10)第二十一、二十二行显示几个 GC 工作线程的存活时间,单位是毫秒;
(11)第二十三、二十四行显示 GC 线程花费在其他任务中的耗时,单位是毫秒,可以看到这部分时间非常少;
(12)第二十五行显示清空 CardTable 的时间,RS 就是依靠 CardTable 来记录哪些是存活对象的;
(13)显示其他几个任务的耗时,比如选择 CSet(Collection Sets)的时间、Ref Proc(处理弱引用、软引用的时间)、Ref Enq(弱引用、软引用入队时间)和 Free CSet(释放被回收的 CSet 中区域的时间,包括它们的 RS);
(14)显示比较熟悉的 GC 回收的整体情况,这里显示了 eden 区一共 32MB 被清空,survivor 区没有释放对象,整个堆空间没有释放空间。用户 CPU 耗时 0.16 秒,实际耗时 0.04秒;
5.4.7. G1 相关的参
1.-XX:+UseG1GC
:打开 G1 的开关;
2.-XX:MaxGCPauseMillis
:用于指定目标最大停顿时间。如果任何一次停顿超过这个设置值,G1 就会尝试调整新生代和老年代的比例、调整堆大小、调整晋升年龄等,试图达到预设目标。对于性能调优来说,如果停顿时间缩短,对于新生代来说,这意味着很可能要增加新生代GC的次数。对于老年代来说, 为了获得更短的停顿时间,在混合GC时,一次收集的区域数量也会变少,这样无疑增加 了进行Full GC的可能性;
3.-XX:ParallelGCThreads
:用于设置并行回收时 GC 的工作线程数量;
4.-XX:InitiatingHeapOccupancyPercent
:可以指定当整个堆使用率达到多少时,触发并发标记周期的执行。默认值是 45,即当整个堆的占用率达到 45% 时,执行并发标记周期。该值一旦设置,始终都不会被 G1 修改,这意味着 G1 不会试图改变这个值来满足 MaxGCPauseMillis 的目标。如果该值设置得很大,会导致并发周期迟迟得不到启动,那么引起 Full GC 的可能性也大大增加,反之,一个过小的 InitiatingHeapOccupanyPercent 值会使得并发标记周期执行非常频繁,大量 GC 线程抢占 CPU,导致应用程序的性能有所下降。