垃圾回收器在执行某些垃圾回收任务时会暂停所有应用程序线程。这些暂停有时被称为Stop-The-World暂停,因此尽可能避免这种情况是GC调优的主要目标,因为它们会对Java应用程序的性能产生巨大影响。
调整堆大小
垃圾回收调优的第一步是调整堆的大小。这是因为如果堆太小,则会发生太多的GC以回收内存,这会降低整体应用程序吞吐量,如果堆太大,那么GC发生概率会少,一旦发生GC则需要很长时间,这样你的应用的响应时间指标就会受到影响。并行收集器特别容易受到此问题的影响,因此如果您需要大堆并且暂停时间较短,那么您应该尝试使用G1GC收集器。
旁注:因为Java 9和Shenandoah垃圾收集器在撰写本文时仍被视为“实验性”时,不推荐使用并发标记扫描(CMS)收集器。因此,如果您正在运行在线交互式应用程序,那么G1GC应该是您的默认选择,如果您正在运行脱机批处理应用程序,那么并行收集器应该是您的首选。
堆的大小由两个值控制:使用ms标志指定的初始值和使用mx标志指定的最大值。
-Xms1g -Xmx8g
堆的初始和最大大小允许JVM根据工作负载自动调整堆大小。如果JVM遇到内存压力并且观察到GC执行过多,它会不断增加堆,直到内存压力消失,或直到堆达到其最大大小。如果内存压力很低,JVM还可以通过缩小堆大小来决定减少暂停时间。这个过程称为自适应大小调整,它不仅可以调整堆的整体大小,还可以调整年轻代和老代的大小和比例。
如果您花时间精细调整应用程序的GC行为和大小,则可以选择关闭自适应大小调整。这可以节省JVM计算堆大小所需的一小段时间。您可以通过将标志设置UseAdaptiveSizePolicy为false 来执行此操作。
-XX:-UseAdaptiveSizePolicy
此外,将初始堆大小设置为与最大堆大小相同的值或将初始新gen大小设置为与最大新gen大小相同的值只能关闭大小自适应调整行为的一部分。
强烈建议的设置最大堆大小的准则是最大堆大小不应超过计算机上的物理内存量。如果您运行多个JVM,则最大堆大小的总和不应超过计算机的物理内存。
设置最大堆大小的更一般建议是:在完整GC之后堆占用大约30%,要计算此值,您可以在GC日志中查找完整GC发生的条目,并观察GC完成时使用的内存量。或者,您可以运行应用程序,直到它达到稳定状态,然后使用jconsole或强制使用完整的GC jcmd。
调整GC性能
如果启用了自适应大小调整,则可以使用MaxGCPauseMillis标志来调整GC行为,此标志设置最大GC暂停时间的目标,当与Parallel收集器一起使用时,JVM将调整年轻一代和老一代的大小,以便尝试达到目标,然后它将调整堆的大小,以便在GC中花费的时间不超过某个值,默认情况下为1%。
G1GC的目标之一是它只需要很少的调整,因此,在G1GC中,一个调整参数MaxGCPauseMillis执行以下所有优化,以尝试实现指定的暂停时间目标:
- 调整堆的大小
- 更快开始后台处理,
- 调整阈值:对象成为老生代的对象的时间期限,
- 调整在混合GC循环期间处理的旧区域数。
在G1GC中,标志的默认值为200 ms。虽然你可能想把它设置成一个非常小的20毫秒,但请注意,为了达到这个目的,垃圾收集器会将年轻一代收缩到一个非常小的尺寸并收集较少的老一代,这最终会导致老一代垃圾过多的情况,系统必须执行完整的GC,这是不可取的。
修复并发模式故障
G1GC是一个并发收集器,这意味着当应用程序线程仍在运行时,垃圾收集进程的某些阶段可以并发运行,并且由于正在运行的应用程序可以继续产生垃圾,我们可能会遇到应用程序耗尽老生代内存而垃圾收集器仍在垃圾收集过程中的情况。换句话说,正在运行的应用程序生成的垃圾比它可以清理的速度快。这种情况称为并发模式故障、失效故障或疏散故障,具体取决于故障发生的时间。如果您在GC日志中看到很多这些错误,解决方案是增加堆的大小,更早地启动G1后台处理,或者通过使用更多后台线程来加速GC处理。
要更频繁地执行G1后台活动,您可以降低触发G1循环的阈值。这是通过减少InitiatingHeapOccupancyPercent标志的值来实现的。
-XX:InitiatingHeapOccupancyPercent=45
默认情况下,此标志设置为45。这意味着当堆填充45%时会触发GC循环。减少此值意味着GC会更早且更频繁地触发。但应注意的是,该值不会设置为太低而导致GC过于频繁发生。
要增加后台线程数,请使用该ConcGCThreads标志。
-XX:ConcGCThreads=4
此标志的默认值设置为ParallelGCThreadss加2除以4.只要计算机上有足够的CPU可用,就可以增加此值而不会导致任何性能损失。
如果调整堆大小并调整收集器对您不起作用,那么您可以尝试另一个收集器。如果你仍然没有取得好成绩,那么你需要考虑调整应用程序代码本身。