透彻理解JVM中垃圾回收GC生产参数,停顿时间+执行效率相关参数

2023年 8月 29日 89.9k 0

停顿时间相关参数

部分垃圾回收器实现了GC执行时应用最大停顿时间的功能,所以提供参数用于应用控制停顿时间。另外,GC为了满足停顿时间,会设计和实现一些动态算法来调整堆空间,从而满足停顿时间这个目标。本节介绍相关参数。

该参数表示GC的最大的停顿时间。不同GC对于该参数的行为不一致,具体来说:

1)若Parallel GC中GC执行的时间超过该值,将导致调整新生代和老生代的大小(参数UseAdaptiveSizePolicy设置为true)。参数的默认值为4294 967 295,大约为50天(所以通常不会触发这个调整策略)。

2)若G1中GC执行的时间超过该值,将导致调整新生代的大小和混合回收时分区的个数(如果没有固定新生代大小就会自动调整)。参数在G1中的默认值为200,表示GC的最大停顿时间为200毫秒。

3)在CMS中设置参数无效果。

该参数表示两次Minor GC发生之间最小的间隔时间。参数的默认值为0,自动被设置为MaxGCPauseMillis+1,表示GC最小间隔1毫秒。

该参数表示Minor GC最大的目标停顿时间。从JDK 8开始被标记为丢弃,与Max-GCPauseMillis功能类似。使用该参数在UseAdaptiveSizePolicy设置为true时才生效。参数的默认值为4 294 967 296,大约为50天。

该参数是垃圾回收时间与非垃圾回收时间的比值,垃圾回收时间占比期望为

。例如,-XX : GCTimeRatio = 19时,表示5%的时间用于垃圾回收。

1)默认值为99,即1%的时间用于垃圾回收。

2)在G1中该默认值被修改为9,即10%的时间用于垃圾回收。

3)在CMS中设置无效果。

执行效率相关参数

本节介绍执行效率相关参数,对GC来说,效率主要体现在分配和回收上。

以分配为例,为了能高效地分配,设计了TLAB的分配方法,减少了多个Mutator分配时锁的竞争。但是TLAB的使用可能带来碎片化的问题,所以提供了一系列的参数用于设置TLAB大小、TLAB自动调整等参数。以回收为例,一些GC实现会提供局部标记栈来加速GC的执行,而局部标记栈的大小会影响执行效率,所以提供参数用于设置其大小。其他与GC执行效率相关的参数都在本节介绍。

在复制对象时(并行复制中会涉及转移对象),如果待转移对象是数组类型,将数组拆分成多个子对象,便于线程的并行化复制。在Parallel GC和G1中行为稍有不同:

1)Parallel GC中参数PSChunkLargeArrays为true且数组的长度超过1.5×ParGCArray-ScanChunk时,数组会被截断成多个小对象转移。

2)G1中,在复制对象时,如果待转移对象是数组类型,当数组的长度超过2×ParGCArray-ScanChunk时,数组会被截断成多个小对象转移。

在Parallel GC中控制新生代对象晋升到老生代的阈值。如果该参数为true,则表示不允许调整晋升阈值,并且对象晋升的阈值为最大值(0)。即只需要经过一次Minor GC,对象就会晋升到老生代。参数的默认值为false。

该参数为在Parallel GC中控制新生代对象晋升到老生代的阈值。如果该参数为true,则表示不允许调整晋升阈值,并且晋升的阈值为最大值(15)。即需要经过15次Minor GC,对象才能晋升到老生代。参数的默认值为false。

在进行Parallel GC的串行/并行压缩时,该参数为true,会先执行MinorGC。

在进行Serial GC的串行压缩时,以及在CMS的Full GC、CMS中进行再标记时,该参数为true,会先执行Minor GC。

如果GC请求来自Java应用代码System.gc,当该参数为true时,并发执行垃圾回收;当该参数为false时,CMS串行执行,G1/Shenandoah并行执行。参数的默认值为false。

如果GC请求来自JNI Locker,当该参数为true时,并发执行垃圾回收;

当该参数为false时,CMS串行执行,G1/Shenandoah并行执行。参数的默认值为false。

该参数在JDK 15中被移除,原因是参数可能导致潜在的死锁。更多信息可以参考Jira。

在G1中,当调整新生代的大小时,额外扩展一定比例的内存。参数的默认值为5,表示额外扩展5%的新生代大小。

注意,在原文注释中,该参数用于GC Locker的情况,但是现在的作用已经发生了变化。

在执行GC Locker请求时又触发了GC的情况下,因为GC动作需要等待JNILocker释放才能执行,为了避免JNI Locker阻塞时间太长而导致GC执行不及时,从而影响Mutator可用的内存,所以设计了重试机制,该参数控制GC重试的次数。

参数的默认值为2,表示JNI Locker释放后最多连续执行2次垃圾回收来满足Mutator的内存分配请求,超过2次GC则分配失败。

为什么只在GC Locker的情况下才会重试?一个可能的情况是:当多个线程执行的时候,有线程请求执行GC;有线程正在执行JNI的代码访问Java对象,此时多个线程的分配请求将推迟到GC完成之后。当JNI代码退出时,如果发现执行期间其他线程有GC请求,会设置GC Locker活跃。但多个线程会同时请求分配内存,这可能会导致一些线程连续多次不能分配成功,所以希望再尝试几次回收,避免线程无法成功分配到内存。

Mutator使用TLAB分配内存,可以减少锁冲突。关于TLAB的信息,可以参考3.2.1节。参数的默认值为true,表示使用TLAB。

该参数用于在GC运行结束后动态地调整TLAB的大小,适用于Serial/ParNew/Parallel GC/G1/Shenandoah。

该参数将Mutator新分配的TLAB初始化为0。参数的默认值为false,表示不初始化TLAB。

该参数表示TLAB块的大小的最小值。默认为2KB,当小于该值时,TLAB将使用2KB。

该参数表示TLAB块的初始值大小。参数的默认值为0。如果没有设置,那么JVM一般会自动推断,推断的方法是根据所有线程历史使用的TLAB个数和堆空间大小计算。

在TLAB的使用过程中会记录分配TLAB的个数,在GC执行时会将个数存放在历史数据中,然后预测未来TLAB使用的个数。参数TLABAllocationWeight控制的是预测时最新一次TLAB个数对预测值的贡献。参数的默认值为35,表示最新个数在预测值中会贡献35%,记预测值为desired_tlab。

另外,在TLAB使用过程中可能存在浪费的情况,按照概率,平均浪费为50%(实际浪费率远低于该值)。为了准确地控制浪费率,提供了参数TLABWasteTargetPercent,浪费率

,记为refill_target,参数TLABWasteTargetPercent的默认值为1,表示TLAB有50%的浪费率,如果将参数TLABWasteTargetPercent设置为5,则表示TLAB有10%的浪费率。

TLABSize的推断公式为

进行TLAB分配时,当请求的内存字节数小于一定阈值

时,参数TLABRefillWasteFraction的默认值为64,即TLAB剩余1/64的空间大小,且无法满足Mutator的分配请求时将被丢弃。丢弃意味着这部分剩余空间将被填充一个dummy对象。

还有另外一种情况,如果遇到TLAB剩余空间不足,无法满足Mutator的分配请求,但还不能丢弃的情况(剩余空间还比较多),此时会直接在堆(Heap)中分配对象。这种情况每发生一次,将TLAB浪费(丢弃)的阈值

的增加浪费加上参数TLABWasteIncrement,参数的默认值是4。

参数TLABWasteIncrement是为了防止出现因阈值设置较小,TLAB一直无法丢弃,但剩余空间又无法满足Mutator的分配请求的情况。

在ParNew/G1中,对象晋升会在PLAB中请求内存,若PLAB无法满足分配请求,且PLAB中剩余的内存字节数小于PLABSize和参数ParallelGCBufferWastePct比例的乘积,即

,则PLAB剩余空间将被丢弃。参数的默认值为10,表示若PLAB中剩余10%且无法满足对象晋升的分配请求时将被丢弃。丢弃意味着这部分剩余空间将被填充一个dummy对象。

参数ParallelGCBufferWastePct与TLAB中TLABRefillWasteFraction的含义类似。

ParNew是在To空间分配,G1在新生代分配。

以上参数控制在Minor GC执行结束后是否动态调整PLAB的大小。其中PLAB的作用与TLAB类似,不过PLAB分为两类,分别称为YoungPLAB和OldPLAB,其中YoungPLAB用于Minor GC中活跃对象从Eden到Survivor分区的分配,而OldPLAB则用于Minor GC中活跃对象从Survivor到Old的晋升。

参数ResizePLAB在ParNew中仅仅调整YoungPLAB的大小;在G1中调整YoungPLAB和OldPLAB的大小。参数的默认值为true,表示允许调整。

参数ResizeOldPLAB用于调整CMS中OldPLAB的大小。参数的默认值为false,表示不允许调整,并且参数在JDK 14中被移除,所以也不适用于JDK17。

参数ResizePLAB和ResizeOldPLAB不适用于Parallel GC,Parallel GC并未实现PLAB大小调整的功能,而是使用固定大小。

YoungPLAB的调整通常需要所有GC工作线程的统计信息,如YoungPLAB分配的次数、大小、碎片化,然后预测后续YoungPLAB的大小。YoungPLAB使用撞针的分配方法,虽然有一定的碎片化情况(在PLAB的尾部可能存在碎片化),但能明显地提高性能。

在CMS中,OldPLAB的设计会相对困难。通常在CMS中通常会避免动态调整老生代OldPLAB。原因是动态调整OldPLAB可能会加剧老生代碎片化。

主要是因为CMS老生代是FreeList的方式,对于大小为[0,256]字的对象,直接在List高速分配,对于超过256字大小的对象,在一个字典中分配。如果要在CMS中为OldPLAB增加性能效率,需要为所有不同大小的List分配缓存,为每一个GC工作线程都分配缓存,这样的List才能做到高效无锁的分配,但这样的设计将导致更大的碎片化。为了在分配效率和碎片化之间取得平衡,CMS实际上是为所有的GC工作线程缓存一个[0,256]字大小的列表,但是会根据GC使用字大小的情况预留一些空间,比如16字大小可能会预留5个空间。

使用ResizeOldPLAB会在每次GC结束后调整列表预留的个数,随着应用的执行,可能会导致碎片化越来越严重。在运行的后期可能会因为碎片化触发Full GC。

所以通常在CMS中禁止自动调整OldPLAB。

参数YoungPLABSize指定Parallel GC/ParNew/G1执行过程中YoungPLAB的大小。参数OldPLABSize指定Parallel GC/ParNew/G1执行过程中OldPLAB的大小。参数YoungPLAB-Size的默认值为4096,表示YoungPLAB缓冲区的大小为4096字。参数OldPLABSize的默认值为1024,表示OldPLAB缓冲区的大小为1024字。

Parallel GC中不支持动态调整PLAB,Serial GC是串行的,无须支持这两个参数。

该参数是对整个Survivor空间进行划分的比例。

G1中利用该值计算To空间分区的大小,计算方式为

,其中Survivor_size是两个Survivor分区空间的大小,如果TargetSurvivorRatio增大,那么用于下一次To空间的空间变大,晋升到Old分区的概率会减小。

在Serial和CMS中可以通过该参数调整晋升对象的次数。该参数越大,To空间越大,晋升对象溢出的概率越小。

参数的默认值为50,表示To空间占Survivor分区的50%。

另外,该参数还与TargetPLABWastePct一起用于调整PLAB的大小。

TargetPLABWastePct是用于控制计算动态PLAB的大小参数之一。该参数与参数TLABWasteTargetPercent的作用类似,为了准确控制PLAB浪费率,提供了参数TargetPLAB-WastePct。

在PareNew中,浪费率

记为refill_target,公式的含义是将Survivor分区中To分区的浪费率看作整个Survivor的浪费率,在ParNew中参数TargetPLAB-WastePct不能大于参数TargetSurvivorRatio,否则表示没有任何浪费,就没有意义。

在G1中的用法也与之类似,只不过公式稍有不同,参数TargetPLABWastePct表示浪费的比例,即

。关于G1的参数,在12.2节中还会介绍。

参数TargetPLABWastePct的默认值为10,表示PLAB的浪费比例。

在计算ParNew/G1中PLAB的大小时,使用的是衰减平均值的方法,通过历史PLAB的大小预测未来PLAB的大小。该参数表示最新的PLAB大小对预测值的贡献度。

参数PLABWeight的默认值为75,表示预测下一次YoungPLABSize时最新YoungPLAB-Size的贡献度为75%。

参数OldPLABWeight的默认值为50,表示预测下一次OldPLABSize时最新OldPLAB-Size的贡献度为50%。该参数仅适用于CMS,且在JDK 17中被移除。

Mutator分配对象时,若对象超过一定的阈值,则直接在老生代中分配,该参数控制阈值。参数PretenureSizeThreshold的默认值是0,表示对象不管多大,都是先在Eden中分配内存。该参数不适用于G1、Shenandoah,它们设置了大对象分区(G1中可以认为大对象分区是老生代分区,Shenandoah中只有一个代),当Mutator分配的对象大小超过一定的阈值时,直接在大对象分区中分配。

当JVM向OS提交内存后,把提交的内存初始化为0,这将访问内存(可能引起OS的缺页中断)。在Shenandoah中,该参数会被强制设置为false。使用该参数的目的是在初始化时确保内存被分配,在Mutator运行过程中不会再出现按需使用的情况,这会降低初始化时的性能,但能加速运行时性能。该参数的默认值为false,表示不对提交的内存初始化。

当JVM向OS提交内存后,把提交的内存初始化为0,多个线程并行执行提交,每个线程提交的最小值。该参数的默认值与OS相关,在Linux系统中默认值为4MB。

Minor GC中采用深度遍历进行复制,需要使用标记栈,通过该参数可设置标记栈的最大值。在64位系统中,默认值为512MB,在非64位系统中为4MB。

Parallel GC中线程私有标记的大小是固定设置,不可以通过参数设置。

在64位系统中,默认值为512KB,在非64位系统中为64KB。

该参数表示Minor GC中标记栈的大小。在64位系统中默认值为4MB,在非64位系统中为32KB。

该参数表示ZGC中并发标记过程中多个GC工作线程标记栈的最大值。参数的默认值为8GB。

该参数表示Java语言中引用对象的标记策略控制。共分以下两种。

0:引用者(Reference)位于老生代,被引用者(Referent)位于新生代,只有这样的Referent才能作为活跃对象。该策略被称为ReferenceBasedDiscovery。

1:被引用者(Referent)位于新生代,而引用者(Reference)既可以位于新生代,也可以位于老生代,将这样的Reference作为活跃对象进行遍历。该策略被称为ReferentBasedDiscovery。

和策略0相比,策略1可以更快地把引用对象加入活跃对象中,但是可能会带来额外的浮动垃圾。参数的默认值为0。

该参数控制Java的引用是否并行处理,适用于Parallel GC/CMS/G1,ZGC和Shen-andoah是并发执行的,故不受该参数控制。参数的默认值在不同的GC和不同的JDK版本中不同,例如在JDK 17中Parallel GC设置为false,表示不开启并行处理;在G1中设置为true,表示开启任务处理。

该参数控制Java的并行引用处理过程中是否使用任务均衡机制来加速引用处理。引用处理的任务均衡指的是对同一类型引用任务队列(queue)进行平衡。参数的默认值为true,表示进行任务均衡。

这些参数用于控制是否允许在GC运行时检查GC占用的时间和空间是否超过一定的阈值,如果超过并且达到一定的次数后,在Full GC执行完成后对Mutator的对象分配请求会强制返回NULL(表示OOM异常)。次数通过参数
AdaptiveSizePolicyGCTimeLimitThreshold(后被改名为GCOverheadLimitThreshold)控制,但是该参数是开发参数,默认值为5,不能在发布版本中修改。

GC时间占用的比例超过参数GCTimeLimit定义的阈值,表示系统负载较重。参数的默认值为98,当参数设置为100时,表示不进行检查,相当于将参数UseGCOverheadLimit设置为false。

若堆空间中新生代和老生代可用空间的比例低于参数GCHeapFreeLimit定义的阈值,则表示系统几乎没有空闲的空间。参数的默认值为2,表示当FullGC发生时,老生代空闲的内存小于老生代内存的2%,且新生代空闲的内存小于新生代内存的2%,表示系统负载较重,后续在Full GC进行之后可能在对象分配时强制返回NULL。

需要注意的是,该参数仅仅统计Full GC的运行情况。

参数UseGCOverheadLimit的默认值为true,参数GCTimeLimit的默认值为98,参数GCHeapFreeLimit的默认值为2。这3个参数可以简单总结为,当发现有5次Full GC(不需要连续5次GC都是Full GC,也不是最近5次Full GC)时,垃圾回收时间占比达到98%,并且新生代和老生代的空闲内存都小于各自分区的2%时,在这次Full GC后Mutator再有内存请求,将终止运行。

在新生代的串行/并行复制算法中,可以使用硬件指令(prefetch)预取数据,用于减少Cache Miss。例如64位Linux通过prefetch0指令将memory的数据预读取到cache中,以减少访问主存的指令执行时的延迟。在Linux的64位系统中,该值被设置为576。该参数与硬件相关,不同的硬件中设置不同。

在新生代的串行/并行复制算法、并发扫描、Full GC中,会扫描内存。

在Linux 64位系统中,
PrefetchScanIntervalInBytes的值被设置为576。该参数与硬件相关,不同的硬件中设置不同。

该参数用于设置是否允许优化处理卡表。在执行store指令,C1/C2生成代码时,如果发现卡表已经被设置,则可以不再设置卡表。在Serial/Parallel GC中不会涉及并发处理,所以非常简单。CMS可能涉及并发处理卡表,所以在检查卡表时还需要通过内存屏障指令进行数据同步。参数的默认值为false。

该参数用于设置是否允许GC工作线程和CPU绑定,目前仅支持Solaris系统。参数的默认值为false,在JDK 17中已经被删除。

使用该参数,将根据亲缘性,把GC任务固定到对应的线程上。GC任务指的是Parallel GC中的复制/标记等。参数的默认值为false,在JDK 17已经被删除。

如果JVM没有支持绑定CPU,是否存在其他方法实现线程和CPU的绑定?在Linux系统中可以通过taskset命令实现绑定。

在分代垃圾回收器中,每次垃圾回收完成后可以调整新生代的大小,在调整时,根据线程的个数乘以该参数值,增加到新生代的空间中。

不同平台上该参数的默认值不同。在Linux/X86系统中该值为4KB。

该参数指定当连续进行一定次数的垃圾回收动作后,将输出警告信息(主要是进行了几次垃圾回收、请求的内存等信息)。参数的默认值为0,表示不输出。

根据强分代理论,如果对象长期存活,可能在后续也会继续存活,可以把这些可能长期存活的对象放在一起,然后在Full GC时跳过这部分空间,可以节约回收的时间。但是在运行过程中,这些长期存活的对象一定会有对象死亡,此时按照标记-压缩算法就需要对这些对象占用的空间进行压缩,无法节约时间。为此,引入参数MarkSweepDeadRatio,表示当死亡对象尚未超过空间的一定阈值时,仍然不移动压缩这些活跃对象。

参数的默认值为5,表示死亡对象浪费的最大占比为堆空间的5%。

Parallel GC在初始时将该参数的默认值修改为1。

在JDK 17中,G1开始支持该参数。

由于使用参数MarkSweepDeadRatio会跳过部分死亡对象,因此会导致Full GC压缩效率不高,所以经过一定次数的Full GC后,会强制执行一次不跳过任何死亡对象的标记-压缩。参数
MarkSweepAlwaysCompactCount控制跳过死亡对象的标记-压缩次数。参数的默认值为4,表示每4次跳过死亡对象后,第5次Full GC不会跳过死亡对象。

在并行复制算法和并发标记中,可能涉及多个线程的任务均衡,从其他线程的标记栈取数据,VM为了保证每个线程执行的效率,会为每个线程保留一定数量的对象,保留对象暂时不被其他线程窃取。参数GCDrainStackTargetSize控制线程标记被保留的数量,默认值为64,表示保留64个对象用于线程窃取。

使用该参数,调用System.gc()时不触发垃圾回收。其默认值为false,表示触发Java代码中调用System.gc()会触发Full GC。

在一些Java代码RMI的实现中,有一个周期性线程调用System.gc(),而Full GC又影响性能,当发现内存满足应用的需求时可以使用该参数。

篇文章给大家讲解的内容是JVM中垃圾回收相关参数介绍:GC通用参数,GC生产参数,停顿时间+执行效率相关参数

  • 下篇文章给大家讲解的内容是JVM中垃圾回收相关参数介绍:GC通用参数,GC生产参数,大页和NUMA参数+GC日志相关参数+其他参数
  • 感谢大家的支持!
  • 相关文章

    服务器端口转发,带你了解服务器端口转发
    服务器开放端口,服务器开放端口的步骤
    产品推荐:7月受欢迎AI容器镜像来了,有Qwen系列大模型镜像
    如何使用 WinGet 下载 Microsoft Store 应用
    百度搜索:蓝易云 – 熟悉ubuntu apt-get命令详解
    百度搜索:蓝易云 – 域名解析成功但ping不通解决方案

    发布评论