【Java 8 GC 调优】并行GC
并行GC,也称为 吞吐量GC,是与 串行GC 类似的 分代GC。其主要区别在于,用多个线程加快垃圾收集速度。
可通过命令行选项 -XX:+UseParallelGC 启用 并行GC。默认情况下,使用此选项后,Minor 和 Major GC 都是并行执行,以进一步减少垃圾收集的开销。
在具有超过8个硬件线程的机器上,并行GC 会使用固定占比的数量作为GC线程数。
硬件线程数较大时,该占比为 5/8 。硬件线程数小于8时,GC线程数就是硬件线程数。在特定的平台上,该占比会降到 5/16 。可通过命令行选项指定 GC 线程数(稍后介绍)。
在单处理器机器上,因为并行执行(如,同步)的开销,并行GC 的表现不如 串行GC。
但对于中大型堆的应用程序来说,当运行在双处理器机器上时,并行GC 的性能通常会比 串行GC 稍好一些。有2个以上处理器时,并行GC 的表现会更好。
GC 线程数量 可通过命令行选项 -XX:ParallelGCThreads=<N> 来控制。
在已通过命令行显式设置堆大小的情况下,堆大小对良好性能的影响对 并行GC 和 串行GC 是一样的。但是启用 并行GC 应该会缩短暂停时间。
老年代碎片化
因为 多个GC线程 同时参与到 Minor GC 中,所以可能会在 “从 新生代 提升到 老年代” 的过程中产生一些碎片。每个 GC 线程都会在 Minor GC 中预留一部分老年代的空间用于“提升”操作。而这个划分 “提升缓冲区” 的操作会产生碎片。
减少 GC 线程数量 和 增大老年代容量 可以降低此“碎片化效果”。
“代” 划分
如前所述,并行GC 中“代”的划分方式不同。如下图所示:
并行GC Ergonomics
在服务器类机器上,JVM 默认选择 并行GC。此外,并行GC 使用了一种自动调整的方法,它允许你指定特定的行为,而不是“代”容量或其它低级的细节调整。你可以指定 GC 暂停时间的最大值、吞吐量、内存占用(堆大小)。
3个 目标
GC 暂停时间最大值
可通过命令行选项 -XX:MaxGCPauseMillis=<N> 指定该最大值。
它会被解释为对GC的提示:暂停时间不能超过 <N> 秒。默认情况下没有最大暂停时间目标。如果指定了暂停时间目标,那么GC会调整 堆大小 及 其它相关参数,以尝试让暂停时间小于指定的值。
这些调整可能会降低应用程序的总吞吐量,而且并不总是能满足暂停时间目标。
吞吐量
吞吐量目标是根据 GC所花费时间 与 GC之外所花费时间(称为“应用程序时间”)来衡量的。
应用程序时间占比高就是吞吐量高。
该目标是通过命令行选项 -XX:GCTimeRatio=<N> 来指定的。它表示 GC 时间占总时间的 1/(1+<N>) 。
例,-XX:GCTimeRatio=19 设置的目标为 GC 时间占总时间的 1/20。默认值为 99,即 GC 时间占总时间的 1% 。
内存占用(Footprint)
可通过选项 -Xmx<N> 指定 堆的最大内存占用。此外,GC 还有一个隐含的目标:在满足其它目标的情况下,使堆尽可能小。
目标优先级
这些目标的优先级顺序如下:
- 最长暂停时间
- 吞吐量
- 最小内存占用
首先需满足最长暂停时间目标。只有满足它后,才去实现吞吐量目标。同样的,只有前两个目标实现后,才会考虑内存占用的目标。
调整“代”大小
GC 保留的统计信息(如 平均暂停时间)在每次收集结束时都会被更新。然后检查是否满足上述目标,如有必要,就会调整“代”的大小。也有例外情况,统计信息更新 和 “代”大小调整策略 会忽略 显式GC操作(如,调用 System.gc() )。
GC 会用“代”大小的固定百分比来增大或缩小“代”,使得它满足期望的规模。增大与缩小的比例不同。
默认情况下,增大的比例为 20%,缩小的比例为 5% 。
可通过命令行选项 -XX:YoungGenerationSizeIncrement=<Y> 和 -XX:TenuredGenerationSizeIncrement=<Y> 分别调整 新生代 和 老年代 的增长比例。
-XX:AdaptiveSizeDecrementScaleFactor=<D> 可调整缩小的比例。如果增大的比例为 X%,那么缩小的比例为 (X/D)%
如果在程序启动时,GC决定增大“代”,那么它会为“增大比例”加一个补充量。此补充量并不是长期有效的,它会随着 GC 次数的增加而衰减。这个补充量的目的是为了提高启动性能。
“缩小比例”没有补充量。
如果 最长暂停时间 不达标,则一次只缩小一个“代”的容量。
如果两“代”的暂停时间都未达标,则会先缩小暂停时间更长的“代”。
如果 吞吐量 不达标,则两“代”容量都会增大。每“代”按照各自占GC总时间的比例增大。
例如,如果新生代的GC时间占总GC时间的25%,且新生代的完整“增大比例”为20%,则此次新生代容量会被增大5% (25% * 20% = 5%)。
默认堆大小
除非通过命令行指定了堆的初始容量和最大容量,否则将根据机器上的内存量计算。
Client JVM 中 堆大小的 默认初始值和最大值
如果物理内存不超过 192MB,则默认最大堆容量为物理内存的一半。否则默认堆容量为物理内存的 1/4 。
例如,如果计算机物理内存为128MB,则最大堆容量为64MB;如果物理内存大于等于1GB,则最大堆容量为256MB。
除非你的程序创建了足够多的对象,否则 JVM 实际上不会使用到最大堆容量。
JVM 在初始化时会分配小得多的内存,称为初始堆大小。该容量至少是 8MB,或者是物理内存的 1/64 。
新生代的默认最大容量是总堆的 1/3 。
Server JVM 中 堆大小的 默认初始值和最大值
Server JVM 中 堆大小的默认初始值和最大值 与 Client JVM 类似,只是默认值可能会更高。
在 32位 JVM 上,如果物理内存大于等于4GB,那么默认最大堆容量可以高达 1GB。
在 64位 JVM 上,如果物理内存大于等于128GB,那么默认最大堆容量可以高达 32GB。
你始终可以直接指定更高或更低的初始堆容量和最大堆容量。见下一节。
指定 堆的 初始容量 与 最大容量
可以通过 -Xms 和 -Xmx 分别指定堆的初始容量与最大容量。
如果你知道程序需要多大的堆才能正常工作,可以为 -Xms 和 -Xmx 设置相同的值。
如果不指定相同的值,则 JVM 会从初始堆大小开始增大堆,直到找到堆容量和性能之间的平衡。
其它参数与选项会影响这些默认值。如需验证这些默认值,可以用 -XX:+PrintFlagsFinal,并在输出中查找 MaxHeapSize 。
如,在 Linux 或 Solaris 中,你可以参照以下命令运行 java:
java -XX:+PrintFlagsFinal -version | grep MaxHeapSize
GC 时间过长 与 OutOfMemoryError
如果垃圾收集耗时过长,并行GC 会抛出异常 OutOfMemoryError。
如果超过 98% 的时间用于垃圾收集,而恢复的堆空间不到 2%,那么会抛出 OutOfMemoryError。
此特性旨在防止应用程序运行了很长时间,但由于堆太小而几乎没有进展。
如有必要,可通过命令行禁用此特性 -XX:-UseGCOverheadLimit 。
测量
并行GC 的详细输出与 串行GC 基本相同。
上一篇: 【Java 8 GC 调优】有哪些 GC