6.5 小结

在前面的这两章中,我们花费了大量的时间深入介绍了垃圾收集(以及各种垃圾收集方法)工作的细节。如果垃圾收集花费的时间超出了你的预期,了解垃圾收集的内部工作原理能帮你决定采取哪些必要的步骤进行性能调优。

现在我们已经了解了所有的细节,让我们回退一步,决定选择什么方法,采用什么标志来调优垃圾收集器。下面是一些问题集合,在调优之前,先试着回答这些问题,它们能帮你理清思路,选择合适的调优措施。

你的应用能够忍受 Full GC 的停顿吗?

如果答案是肯定的,选择 Throughput 收集器能获得最高的性能,同时,使用的 CPU 和堆的大小也比其他的垃圾收集器少。如果答案是否定的,你需要依据可用的堆大小做选择,如果可用的堆较小,你可以选择并发收集器,譬如 CMS 收集器或者 G1 收集器;如果可用的堆比较大,推荐使用 G1 收集器。

使用默认设置能达到你期望的性能目标吗?

尽量首先使用默认的设置。因为垃圾收集技术在不断发展成熟,自动调优大多数情况下取得的效果是最好的。如果使用默认设置没有达到你需要的性能目标,请确认垃圾收集是否是性能瓶颈。查看垃圾收集日志能帮我们了解 JVM 在垃圾收集上花费了多长时间、垃圾收集发生的频率是多少。对于负荷较高的应用,如果 JVM 花在垃圾收集上的时间不超过 3%,即使进行垃圾调优也不会得到太大的性能提升(不过,如果那些指标是你关注的方面,你仍然可以尝试通过调优缩短某些指标)。

应用的停顿时间与你预期的目标接近吗?

如果停顿时间与你预期的目标很接近,调整最大停顿时间的设定可能是你需要做的。如果不是,你需要进行其他的调整。如果停顿时间太长,但是应用的吞吐量正常,你可以尝试减小新生代的大小(如果瓶颈是 Full GC 的停顿,就减小老年代的大小);调整之后,停顿的频率会增加,但是单次停顿的时长会变短。

虽然 GC 的停顿时间已经非常短了,但应用的吞吐量依旧上不去?

这种情况下你需要增大堆的大小(至少要增大新生代)。但是,这并不意味着堆越大越好:更大的堆会导致更长的停顿时间。即便是并发收集器,默认情况下,增大堆也还是意味着增大新生代,因此你会发现新生代的停顿时间变长了。即便是这样,如果有可能,还是应该增大堆的大小,或者增大对应代的大小。

你使用并发收集器吗?是否发生了由并发模式失败引起的 Full GC ?

如果你有足够的 CPU 资源,可以尝试增加并发 GC 线程的数量,或者通过调整 InitiatingHeapOccupancyPercent 参数在更早的时候启动后台清理线程。对于 G1 收集器,如果有混合式垃圾收集尚未完成,并发周期就不会启动。在这个时候,可以尝试降低混合式 GC 的回收目标(Mixed GC count target)。

你使用并发收集器吗?是否发生了由晋升失败引起的 Full GC ?

在 CMS 收集器中,发生晋升失败意味着堆发生了碎片化。这种情况下,我们能做的事情不多:使用更大的堆,或者尽早地启动后台回收都能在一定程度上缓解堆的碎片化。处理这种情况,更好的解决方法可能是使用 G1 收集器。G1 收集器中,疏散失败(To 空间溢出)表明遭遇了同样的情况,但是 G1 收集器能解决碎片化的问题,如果它的后台线程在更早的时候启动,且混合式 GC 的速度更快的话。你可以尝试通过增大并发 G1 收集线程的数目,调整 InitiatingHeapOccupancyPercent,或者降低混合式 GC 的目标来解决 G1 收集器中堆碎片化的问题。