JVM中垃圾回收GC生产参数,GC选择+工作线程+内存设置相关参数

本文着重介绍JVM垃圾回收相关参数。首先阐述用好JVM垃圾回收功能的关键点,以G1为例说明GC调优思路。接着介绍不同类型参数,如生产、实验等参数。还讲解了GC通用、生产、工作线程及内存设置等相关参数,包括参数作用、默认值及不同JDK版本的变化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

JVM中垃圾回收相关参数介绍

用好JVM的垃圾回收功能有两个关键点:一是需要理解GC算法的原理和实现,二是需要知道JVM到底提供了哪些参数,这些参数用于控制什么。

然而实际工作更为困难,一方面是要理解参数,需要理解实现细节;另一方面,JDK不断升级变化,在升级的过程中GC的实现也会发生变化,参数会增加或删除,某些版本中甚至存在一些错误的实现,从而导致参数的含义发生了变化。长期以来,官方提供的参数说明文档是程序员唯一可以信赖的理解参数作用的文档。但是官方提供的文档非常简单,通常只有一句简单的描述,根本不足以满足调参的需要。这一部分着重介绍JVM提供的参数。

要想用好参数,首先需要理解参数的作用。目前生产环境中使用最为广泛的是GC还是分代垃圾回收器,不同GC的实现不同,提供的参数也不同。下面以G1为例演示GC调优的一般思路。首先来看看分代对应用的影响,如下图所示。

根据图中介绍,新生代大小会影响停顿时间,Survival的大小,即Form和To空间会影响Minor GC的回收性能,Eden的大小会影响Mutator的分配,即执行效率,老生代的大小会影响GC执行性能、停顿时间、内存可用空间等,Full GC影响停顿时间。

在GC调优时,应根据应用运行的特点调整相关参数,从而保证应用运行效率高、GC停顿时间短。就G1来说,可以从以下几方面调整参数。

1)堆空间:最大堆和最小堆、目标停顿时间设置、分区大小。

2)新生代:TLAB大小、YoungPLAB大小、ResizeTLAB设置、ResizePLAB设置、SurvivalAge设置。

3)老生代:并发标记触发时机、OldPLAB大小,混合回收时老生代参与回收的限制。

4)硬件特性适配:NUMA-Aware、NV-DIMM。

5)代际管理:存储粒度、并发处理和Minor GC的交互。

6)Minor GC、Mixed GC、Full GC并行的线程数目,以及并行任务均衡和终止机制。

7)引用集处理并发的线程数目。

8)并发标记的线程数目。

9)Java语言中引用回收方式和执行方式。

10)GC过程异常情况的处理:晋升失败、保留内存等。

由此可以看出,GC调优不仅需要掌握相关理论的知识,还需要掌握实现的细节及控制细节的参数。本节将详细介绍各种GC相关的参数。

由于不同的JDK版本参数有所变化,大家通常使用的是LTS版本的JDK,所以本书仅对JDK 8、JDK 11和JDK 17的参数做总结和梳理。

实现中提供了不同类型的参数,比如生产参数、实验参数、诊断参数、可动态调整的参数、验证参数、开发参数等,不同的参数类型主要是告诉使用者参数的作用。

1)生产参数:参数说明中包含product,生产参数表示参数已经非常稳定,经过了长期的验证,可以用在生产环境中。

2)实验参数:使用实验参数时需要添加-XX:+
UnlockExperimentalVMOptions才能开启。这些参数并未经历过大规模的使用,可能存在一定的性能、稳定性风险。通常这些参数配合一些新开发的特性来使用。

3)诊断参数:在诊断JVM系统的内部行为时使用,使用时需要添加-XX:+
Un-lockDiagnosticVMOptions才能开启。该类参数通常会暴露更多的运行时信息,以便使用者理解系统。诊断参数通常会影响性能,所以一般不会直接用在生产环境中,通常用于问题定位。

4)可动态调整的参数:JVM的很多参数在启动后就确定下来,但是还有一些参数可以在系统运行的过程中通过API动态地修改,以便控制或者改变JVM内部运行的机制。

5)验证参数:JVM还提供了一些以Verify开发的系列参数,这类参数通常用于验证JVM的运行状态是否符合运行的预期,是JVM开发和测试非常有用的助手。

6)开发参数:开发参数在普通的Release版本中并不存在,仅仅适用于调试版本。这类参数通常能给JVM开发者提供详细的信息,以便开发者理解JVM运行是否符合预期。

GC通用参数

Hotspot中实现了6种垃圾回收器(GC),虽然每种在实现时都有所不同,但是它们还是有一些共同的地方,例如都需要设置堆大小、TLAB、并行/并发线程数等。本章介绍这些通用参数,其中部分参数适用于所有GC,部分参数适用于某几个GC,少数参数只适用于某一个GC。

GC生产参数

GC选择相关参数

Hotspot提供了6种GC,但只能选择一种使用。每种GC使用一个参数进行控制,满足不同场景的诉求。

该参数使用串行垃圾回收器进行垃圾回收。参数的默认值为false,表示JVM启动后并不使用串行回收。

参数UseParallelGC表示使用并行复制回收新生代,参数UseParallelOldGC表示使用并行标记压缩回收整个堆空间。

当参数UseParallelGC设置为true时,参数UseParallelOldGC也默认设置为true。如果参数UseParallelGC设置为true,UseParallelOldGC设置为false,则使用并行复制回收新生代,使用串行标记压缩算法对整个内存进行垃圾回收。

在JDK 9之前,并行垃圾回收器是默认的垃圾回收器,即参数UseParallelGC默认为true,从JDK 9开始该参数默认为false。

参数UseParallelOldGC在JDK 17中被删除,当设置UseParallelGC为true时,默认使用并行标记压缩回收整个堆空间(相当于只允许UseParallelOldGC为true)。删除参数的原因是并行新生代回收和串行标记压缩配合没有任何实际意义,不应该存在这样的配置。

该参数表示使用CMS进行垃圾回收。该参数默认使用ParNew回收新生代,使用并发标记清除回收老生代,使用串行标记压缩回收整个堆空间。参数的默认值为false,表示不使用CMS进行垃圾回收。

该参数表示使用Shenandoah GC进行垃圾回收。参数的默认值为false,表示不使用Shenandoah进行垃圾回收。

JVM内部会根据机器的性能来推断使用哪种GC进行垃圾回收。GC选择的逻辑如下:

1)参数
NeverActAsServerClassMachine为true,使用Serial回收。

2)参数
NeverActAsServerClassMachine为false、参数NeverActAsServerClassMachine和AlwaysActAsServerClassMachine同时为true,优先使用G1,当G1不可用时(指的是没有编译配置G1)选择ParallelGC,Parallel GC不可用时(没有编译配置Parallel GC)选择Serial回收。

3)参数
NeverActAsServerClassMachine为true,且参数AlwaysActAsServerClassMachine为false,根据硬件信息确认使用哪种模式,判断逻辑为:如果CPU核数大于2个并且内存大于2GB,则优先使用G1,当G1不可用时选择Parallel GC,Parallel GC不可用时选择Serial回收,否则直接使用Serial回收。

另外值得一提的是,参数
NeverActAsServerClassMachine的值还与使用的编译优化模式相关,如果只开启了C1,则参数NeverActAsServerClassMachine为true,如果开启了C2,则参数NeverActAsServerClassMachine为false。

参数
NeverActAsServerClassMachine的默认值与平台相关,在X86平台上C1编译器的默认值为true、C2编译器的默认值为false,参数AlwaysActAsServerClassMachine的默认值为false。

提供两个参数的原因是不同的平台上OS获得的硬件信息会有差别,这可能导致不同平台上JVM的行为不一致,如果发现这样的情况,且需要保证多平台行为的一致性,可以直接通过这两个参数控制。

GC工作线程相关参数

除了串行回收,Parallel GC、CMS、G1、ZGC和Shenandoah中涉及多个线程并行或者并发工作,线程个数可以通过参数控制,本节介绍相关参数。

该参数用于设置并行GC工作线程的数目,参数的默认值为0。如果参数没有设置(即保持默认值),JVM根据机器硬件计算得到并行工作线程数。不同的GC计算略有不同,如下:

对于Parallel GC/CMS/G1/Shenandoah,计算公式如下:

对于ZGC,计算公式如下:

ParallelGCThreads = ncpus×60%

其中ncpus为CPU的核数。

根据CPU个数计算并行线程数的目的是防止在一些高性能计算机上CPU核数非常多,比如可以达到128个,如果不对并行线程数做限制,并行工作线程会非常多,可能会导致应用性能下降。性能下降的主要原因是在并行工作时多线程可能会发生任务均衡,同时多个线程需要同步达到终止状态,当并行线程过多时,可能会因为任务窃取效率低下及大量线程同步而出现整体性能下降。

该参数用于设置并发GC工作线程的数目,参数的默认值为0。JVM内部要求ConcGC-Threads小于ParallelGCThreads,如果设置参数,必须为小于ParallelGCThreads的值;如果没有设置参数,JVM会根据不同的GC计算参数的值。计算方法如表9-1所示。

表9-1 不同GC计算参数ConcGCThreads的方法

该参数用于设置是否允许动态调整并行/并发GC工作线程的数目。对于不同的GC,该参数的作用范围不同。具体如下:

1)Parallel GC中可以调整并行工作线程数,影响Minor GC和Full GC。

2)G1中可以调整并发标记的线程数,也可以调整Refine线程数,还可以调整并行工作线程数。

3)CMS中可以调整并行工作线程数,但是不能调整并发线程数目。

4)Shenandoah中可以调整并行和并发工作线程数。

5)ZGC不受此参数控制,实现了额外的控制逻辑。

这是一种在允许GC工作线程动态调整的实现中(参数
UseDynamicNumberOfGCThreads为true时)智能推断GC线程个数的方法。

具体方法为:使用整个堆内存的大小除以参数HeapSizePerGCThread,得到线程的个数,假定用此种方式推测出GC工作线程个数为Number_Worker1。该方法的逻辑是,假设每个GC工作线程处理的内存大小为HeapSize-PerGCThread。

GC工作线程调整的另外一种智能推断GC线程个数的方法如下:将正在允许的Java线程个数的两倍作为一个上限,假定此种方式推测出GC工作线程的个数为Number_Worker2。

动态GC工作线程的预测值为Min(Max(Number_Worker1,Number_Worker2),ParallelGC- Threads)。

参数HeapSizePerGCThread在不同的平台中的默认值不同,在32位系统中的默认值为128MB,在64位系统中的默认值为128MB×1.3(注意1.3是一个平台系数)。

该参数表示GC线程在运行的时候可以记录线程运行的时间信息,使用一个数组记录这些数据,当数据输出之后,所有的数据将被清除。该参数只有在debug的日志下才会生效。参数的默认值为200,表示每间隔200毫秒记录一次信息。

由于JVM内部重构了参数动态调整的实现,因此该参数不再有效,在JDK14中被移除。

该参数用于指定ncpu的值。该值会影响并行/并发线程的个数。如果指定了该参数,JVM将不再使用运行环境中真实CPU的个数。参数的默认值为-1,表示不指定ncpu的值,由JVM通过系统API获得真实的CPU个数。

注意,运行环境可能是Docker容器,也可能是真实的物理机。

内存设置相关参数

Hotspot提供了参数用于控制堆空间大小、分代GC各个代的大小等。本节介绍相关参数。

该参数用于设置JVM最大可以使用的物理内存,适用于所有的垃圾回收器。该参数是平台相关的参数(例如在X86 32位系统中该值默认为1GB)。当没有设置最大堆空间时,使用该参数智能推断堆空间大小。如果参数没有设置,将使用操作系统API获取系统的物理内存,用于进行堆空间的计算。单位是B,可以兼容G/K/M等符号,如设置为4GB。

参数MaxHeapSize和Xmx用于设置JVM最大可用的堆空间,在32位系统中,参数的默认值为96MB。若没有设置参数MaxHeapSize,则会通过可用物理内存(记为physical_memory)来估算并修正默认值。修正的方式如下:

1)当physical_memory或者MaxRAM指定的可用物理空间小于系统参数默认值的2倍时(例如32位系统,物理空间小于96×2 = 192MB),

2)否则

参数MaxRAMPercentage和参数MinRAMPercentage的默认值分别是25和50,含义是MaxHeapSize理想空间是物理空间的25%,如果物理空间特别小,那么MaxHeapSize不少于物理空间的50%。

参数MaxRAMFraction和参数MaxRAMPercentage的作用类似,两者存在换算关系,如下:

参数MinRAMFraction和参数MinRAMPercentage的作用类似,两者存在换算关系,如下:

参数Xmx、MaxRAMFraction和MinRAMFraction在JDK 10之后被标记为丢弃,而仅使用MaxHeapSize、MaxRAMPercentage和MinRAMPercentage,原因是后者更为直观、便于理解。当然这两类参数目前都还有效,如果同时设置,那么只有后者生效。

该参数用于调整堆空间自动计算的边界值。当MaxHeapSize没有设置时,则JVM会自动计算参数MaxHeapSize,如果设置了参数ErgoHeapSizeLimit,那么JVM会取计算值和ErgoHeapSizeLimit两个之中较小的那个用于最终的计算中。

参数InitialHeapSize用于设置JVM启动后初始化的堆空间大小。如果初始化堆空间没有设置,则JVM会通过可用物理内存来估算InitialHeapSize,计算方式如下:

参数InitialRAMPercentage的默认值为1.526%(即1/64)。参数InitialRAMFraction和参数InitialRAMPercentage的作用类似,两者存在换算关系,如下:

参数InitialRAMFraction在JDK 10之后被标记为丢弃。

废弃InitialRAMFraction使用InitialRAMPercentage的原因是后者更为直观,便于理解。两个参数目前都还有效,如果两个参数同时设置,则只有参数InitialRAMPercentage生效。

参数MinHeapSize和Xms用于设置JVM启动后最小可用的堆空间大小。如果最小堆空间没有设置,则JVM会通过可用物理内存来估算最小堆空间。计算方式如下:

MinHeapSize = Min(InitialHeapSize, OldSize + NewSize)其中OldSize和NewSizie也是来自参数值。

这两个参数用于设置分代垃圾回收器新生代的大小,NewSize的默认值在32位系统中为1MB。如果没有设置NewSize,那么JVM会通过InitialHeapSize来估算NewSize。计算方式如下:

该参数用于设置分代垃圾回收器新生代的最大值(新生代最多可用的空间大小)。如果MaxNewSize没有设置,则MaxNewSize的计算方式如下:

该参数用于设置分代垃圾回收器老生代的大小,OldSize的默认值在32位系统中为4MB。如果没有设置老生代大小,则JVM会通过MaxHeapSize来估算OldSize。计算方式如下:

该参数根据比例设置新生代大小,默认值为2。如果没有设置MaxNewSize和NewSize,可以使用NewRatio计算MaxNewSize和NewSize;如果设置了MaxNewSize和NewSize,则直接丢弃参数NewRatio。

在分代垃圾回收器中,新生代采用复制算法,复制算法中有一个Eden和两个Survivor分区,该参数用于计算Survivor的大小,公式为

参数的默认值为8,表示Survivor分区占整个新生代大小的1/10。

在Parallel GC中,该参数不直接影响Survivor分区的大小,而是通过MinSurvivorRatio和InitialSurvivorRatio设置Survivor分区大小。而G1/ZGC/Shenandoah是分区设置,不需要该参数控制。

在64位系统中,若设置了UseCompressedOops,则Java的堆从地址HeapBaseMinAddress开始,参数的默认值是2GB。

压缩指针仅适用于堆空间小于32GB的情况。在JVM内部的很多地方使用malloc进行内存分配,这些分配的内存都将在HeapBaseMinAddress之下。所以在实际应用中如果遇到本地堆栈的溢出,则可以调整该参数值,避免本地堆和Java堆的冲突。

该参数用于动态调整新生代和老生代的大小,参数的默认值为true。仅适用于Parallel GC,在CMS中即使将该参数设置为true,也会重新定义为false。

在垃圾回收(包含Minor GC或者Full GC)执行的最后,可以根据统计的历史数据来动态地调整各个空间的大小。

当设置参数UseAdaptiveSizePolicy后,会在暂停时间和吞吐量之间取得一个平衡,然后调整新生代和老生代大小。停顿时间和吞吐量的平衡点在于:

1)一个合适的最大GC停顿时间。

2)一个合适的最大Minor GC停顿时间。

3)一个合适的应用程序吞吐量。

4)一个合适的额外内存空间占用。

注意

在调整内存大小时,上述4种方法是有优先级的。停顿时间优先级最高,其次是吞吐量,最后是额外空间占用的情况。只有前面的调整策略不满足的情况下才会使用后面的调整策略。策略如下:

1)如果GC停顿时间大于目标暂停时间(通过参数设置,-XX:MaxGCPauseMillis =nnn)或者Minor GC停顿时间大于目标暂停时间(-XX:MaxGCMinorPauseMillis = nnn),则降低新生代大小以匹配目标暂停时间。

2)否则,如果暂停时间合适,则考虑应用的吞吐量,通过增大新生代的大小满足吞吐量。

3)否则,如果允许调整JVM本地内存使用,则增加新生代的大小,减少Full GC的次数。

在调整过程中使用4个参数,分别如下:

-XX:MaxGCPauseMillis=nnn:不能设置得过小,否则会阻碍吞吐量。如果不设置,那么不使用停顿时间调整新生代、老生代大小。

-XX:MaxGCMinorPauseMillis=nnn:不能设置得过小,否则会阻碍吞吐量。如果不设置,那么不使用停顿时间调整新生代大小。

-XX:GCTimeRatio=nnn:用在垃圾回收上的时间不超过应用运行时间的

。参数的默认值为99,表示垃圾回收时间不应该超过整体时间的1%。


UseAdaptiveSizePolicyFootprintGoal:是否允许调整新生代和老生代的划分比例,以减少JVM本地内存消耗,默认值为true。

该参数允许调整新生代和老生代的大小以减少JVM本地内存的消耗。默认值为true,表示允许调整新生代和老生代的大小。

该参数允许在Minor GC执行结束后动态地计算新生代、老生代的大小划分,仅适用于Parallel GC。该参数需要在参数UseAdaptiveSizePolicy为true时才有效。参数的默认值为true,表示在执行Minor GC后调整新生代、老生代的大小划分。

该参数允许在Full GC执行结束后动态地计算新生代、老生代的大小划分,仅适用于Parallel GC。该参数需要在参数UseAdaptiveSizePolicy为true时才有效。参数的默认值为true,表示在执行Full GC后调整新生代、老生代的大小划分。

执行允许在调用System.gc的GC后动态地计算新生代、老生代的大小的划分(包含Minor GC和Full GC,Minor GC仅用于Full GC触发后,在执行过程中执行一次额外的Minor GC才符合条件,一般的Minor GC不满足该条件。参数ScavenageBeforeFullGC为true,Parallel GC会在Full GC执行前执行一次Minor GC),仅适用于Parallel GC。该参数必须在参数UseAdaptiveSizePolicy为true时才有效。

以上参数是新生代、老生代的大小划分的控制方法之一,使用吞吐量作为目标来动态地调整(吞吐量由GCTimeRatio控制)新生代、老生代的大小划分。

参数
AdaptiveSizeThroughPutPolicy将根据吞吐量调整新生代或者老生代的大小,基本逻辑是:

新生代或者老生代空间变大,垃圾回收发生的概率变小,吞吐量将提高。该参数只会增加内存空间的大小,不会缩小内存空间的大小。当Mutator的吞吐量低于目标阈值时:

1)参数值设置为0时,直接增加的内存大小,且增加的大小通过一个简单公式调整计算。

2)参数值设置为1时,在执行Minor GC后调整新生代的大小。需要通过预测模型来估算新生代的吞吐量变化情况,如果预测吞吐量还会继续增加,则暂时不调整新生代内存的大小,否则通过一个简单的公式来调整大小;在执行Full GC后调整老生代的大小,需要通过预测模型来估算老生代的吞吐量变化情况,如果预测吞吐量还会继续增加,则暂时不调整老生代内存大小,否则通过一个简单公式来调整大小。由于预测模型可能会因为没有足够的历史数据而出现模型预测误差,因此在模型预测的情况下引入了一个额外的参数
AdaptiveSize-PolicyInitializingSteps,控制只有在收集一定次数的数据后才可以使用模型进行预测。

参数
AdaptiveSizeThroughPutPolicy的默认值为0,表示直接调整,无须考虑吞吐量变化的情况;参数AdaptiveSizePolicyInitializingSteps的默认值为20,表示只有经过20次内存增加后才会使用模型进行预测。

内存调整过程涉及两个部分:其一,通过公式调整内存的值,公式是什么;其二,预测模型预测吞吐量的变化,预测模型是什么。这两部分都涉及一些参数。下面结合参数分别看一看公式和预测模型及模型的使用。

Minor GC和Full GC后控制Eden增加。计算公式使用了3个参数:

Eden最后增加的大小为eden_scaled。

其中参数
YoungGenerationSizeIncremen的默认值为20,根据公式,Eden每次调整最少增加20%(记为基础增幅);参数YoungGenerationSizeSupplement的默认值为80,表示JVM运行早期Eden会额外增加80%(记为额外增幅),但是随着Minor GC执行次数的增加,这个值会逐渐变小,最后额外增幅会变成0。

参数
YoungGenerationSizeSupplementDec-ay的默认值为8,控制额外增幅衰减的粒度,表示每执行8次Minor GC,额外增幅减少一半,根据计算可以得到大概发生56次Minor GC后,额外增幅变为0。

在公式中用到了eden_desired、minor_gc_cost、full_gc_cost,它们是Parallel GC运行过程中Eden、Minor GC和Full GC的预测值。下面的参数会介绍它们具体的计算方法。

Full GC后控制老生代增加。计算公式使用了3个参数:

Old最后增加的大小为old_scaled。

其中参数
TenuredGenerationSizeIncrement的默认值为20,根据公式表示Old每次调整最少增加promoted_desired的20%(记为基础增幅);参数TenuredGenerationSizeSupplement的默认值为80,表示JVM运行早期Old会额外增加promoted_desired的80%(记为额外增幅),但是随着Full GC执行次数的增加,这个值会逐渐变小,最后额外增幅会变成0。参数TenuredGenerationSizeSupplementDecay的默认值为2,控制额外增幅衰减的粒度,表示每执行2次Full GC,额外增幅减少一半,根据计算可以得到大概发生14次Full GC后额外增幅变为0。

在公式中使用了promoted_desired、minor_gc_cost、full_gc_cost,它们是Parallel GC运行过程中promoted_desired(预期晋升内存大小)、Minor GC和Full GC相关的预测值,下面的参数会介绍它们具体的计算方法。

在Minor GC和Full GC