写点什么

如何调优 Java 垃圾收集

  • 2021 年 11 月 11 日
  • 本文字数:5532 字

    阅读完需:约 18 分钟

  1. 减少 Full GC 的执行时间

尽量减少传递到老年代的对象数量

分代 GC 是 Oracle JVM 提供的 GC,不包括 JDK 7 及更高版本可以使用的 G1 GC。换句话说,在 Eden 区域中创建一个对象并从 Survivor0 区域转移和转移到 Survivor1 区域,达到一定的次数之后,剩下的对象被移动到老年代。有些对象是在 Eden 区创建的,因为体积大,直接传递到 Old 区。Old 区的 GC 比 New 区的 GC 花费的时间相对更多。因此减少传递到 Old 区域的对象数量可以降低 full GC 的频率,减少传递到 Old 区的对象数量可能会被误解为选择将对象留在 New 区,然而并不是这样,相反,你可以调整新生代区域的大小

减少 Full GC 时间

Full GC 的执行时间比 Minor GC 的执行时间相对较长,因此如果 Full GC 执行时间过长(1 秒或以上),可能会导致多个连接部分发生超时。


  • 如果尝试减小 Old 区域大小以减少 Full GC 执行时间,则可能会发生 OutOfMemoryError 或 Full GC 的次数可能会增加。

  • 如果尝试增加 Old 区域大小来减少 Full GC 的次数,则会增加执行时间。


因此,我们需要将 Old 区域大小设置为“适当”的值


影响 GC 性能的选项




前面我们提到,有人在设置某个 GC 选项时性能很好,为什么我们不像他那样使用那个选项? 原因是不同 web 服务中的对象大小和生命周期是不同的


简单想一下,如果在 A、B、C、D、E 的条件下执行一项任务,而在只有 A 和 B 的条件下执行相同的任务,那么哪个任务完成得更快?从常识的角度来看,答案将是在 A 和 B 条件下执行的任务。Java GC 选项是相同的,设置多个选项并不会提高执行 GC 的速度,相反它可能会使 GC 变慢。


GC 调优的基本原理是将不同的 GC 选项应用到两个或多个服务器上并进行比较,然后通过性能分析选择表现出增强的性能或更好的 GC 时间的服务器设置的选项


下表显示了可能影响性能的 GC 选项中与内存大小相关的选项。


表 1:要为 GC 调整检查的 JVM 选项。


| 分类 | 选项 | 描述 |


| --- | --- | --- |


| 堆区大小 | -Xms | 启动 JVM 时的堆区大小 |


| | -Xmx | 最大堆区大小 |


| 新生代区域大小 | -XX:NewRatio | 新生代与老年代比例 |


| | -XX:NewSize | 新生代大小 |


| | -XX:SurvivorRatio | Eden 区与 Survivor 区的比例 |


我经常使用 -Xms-Xmx-XX:NewRatio 选项进行 GC 调整。 -Xms-Xmx选项是特别需要的,并且如何设置 NewRatio 选项会也对 GC 性能产生重大影响。


有人问 如何设置 Perm 区域大小?可以使用-XX:PermSize-XX:MaxPermSize选项设置 Perm 区域大小,但仅当OutOfMemoryError发生且原因是 Perm 区域大小时才需要设置。


另一个可能影响 GC 性能的选项是GC 类型。下表按 GC 类型显示了可用选项(基于 JDK 6.0)。


表 2:GC 类型的可用选项。


| 分类 | 选项 | 说明 |


| --- | --- | --- |


| Serial GC | -XX:+UseSerialGC | |


| Parallel GC | -XX:+UseParallelGC


-XX:ParallelGCThreads=value | |


| Parallel Compacting GC | -XX:+UseParallelOldGC | |


| CMS GC | -XX:+UseConcMarkSweepGC


-XX:+UseParNewGC


-XX:+CMSParallelRemarkEnabled


-XX: CMSInitiatingOccupancyFraction=value


-XX:+UseCMSInitiatingOccupancyOnly | |


| G1 | -XX:+UnlockExperimentalVMOptions


-XX:+UseG1GC | 在 JDK 6 中,这两个选项必须一起使用。 |


除了 G1 GC,GC 类型是通过在每个 GC 类型的第一行设置选项来更改的。


有很多选项会影响 GC 性能,但是你可以通过设置上面提到的选项来获得显着的效果,但是请记住设置太多选项并不能保证减少 GC 执行时间。


GC 调优程序




GC 调优的过程类似于一般的性能改进过程,以下是我使用的 GC 调整过程。

1.监控 GC 状态

需要监控 GC 状态以检查运行中系统的 GC 状态。请参阅如何监控 Java 垃圾回收

2. 分析监测结果后决定是否需要 GC 调优

检查 GC 状态后,你应该分析监控结果并决定是否调整 GC。


如果分析显示执行 GC 所花费的时间仅为 0.1-0.3 秒,那么无需将时间浪费在调整 GC 上。


如果 GC 执行时间在 1-3 秒,或者超过 10 秒,则需要进行 GC 调优


但是,如果你分配了大约 10GB 的 Java 内存并且无法减少内存大小,则无法调整 GC。在调优 GC 之前,你需要考虑为什么需要分配大内存大小。如果你分配了 1GB 或 2GB 的内存并OutOfMemoryError发生了,则应执行 heap dump 来验证并排除原因。


注意:



Heap dump 是一个内存文件,用于检查 Java 内存中的对象和数据,可以使用 JDK 中包含的 jmap 命令创建此文件。创建文件时,Java 进程停止,因此不要在系统运行时创建此文件。


【一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码


3.设置 GC 类型/内存大小


如果你已决定进行 GC 调优,请选择 GC 类型并设置内存大小。这时候如果你有多台服务器,通过为每个服务器设置不同的 GC 选项来检查每个 GC 选项的差异很重要。

4. 分析结果

在设置 GC 选项后收集数据至少 24 小时后开始分析结果,如果幸运的话,你会找到最适合系统的 GC 选项。如果不是,则应分析日志并检查内存是如何分配的。然后你需要通过更改 GC 类型/内存大小来找到系统的最佳选项。

5. 如果结果令人满意,则将该选项应用于所有服务器并终止 GC 调整。

如果 GC 调优结果令人满意,则将该选项应用于所有服务器并终止 GC 调优。


监控 GC 状态和分析结果




检查运行中的 Web 应用程序服务器 (WAS) 的 GC 状态的最佳方法是使用 jstat 命令。在如何监控 Java 垃圾回收解释了 jstat 命令,所以我将在本文中描述要检查的数据。


下面的例子展示了一个没有做 GC 调优的 JVM(但是它不是操作服务器)。


-gcutil :以百分比的形式显示每个堆区域的使用率,以及 GC 的次数和累计 GC 时间


$ jstat -gcutil 21719 1s


S0 S1 E O P YGC YGCT FGC FGCT GCT


48.66 0.00 48.10 49.70 77.45 3428 172.623 3 59.050 231.673


48.66 0.00 48.10 49.70 77.45 3428 172.623 3 59.050 231.673


复制代码


在这里检查 YGC 和 YGCT 的值,将 YGCT 除以 YGC,然后你得到 0.050 秒(50 毫秒),这意味着在 Young 区域执行 GC 平均需要 50 ms,有了这个结果,你就不需要关心 Young 区域的 GC 调整了。


现在检查 FGCT 和 FGC 的值,将 FGCT 除以 FGC,然后你得到 19.68 秒,这意味着执行 GC 平均需要 19.68 秒,需要进行 GC 调整。


我们可以使用 jstat 命令轻松检查 GC 状态,但是分析 GC 的最佳方法是使用 –verbosegc 选项生成日志,关于如何生成日志和分析日志的工具的详细描述,在上一篇文章中已经解释过了。在用于分析日志的工具中,HPJMeter 是我最喜欢的用来分享 -verbosegc 日志的工具,它易于使用和分析,使用 HPJmeter 可以轻松检查 GC 执行时间的分布和 GC 发生的频率。


如果 GC 执行时间满足以下所有条件,则不需要进行 GC 调优。


  • Minor GC 处理速度很快(50 毫秒内)。

  • Minor GC 不经常执行(大约 10 秒)。

  • Full GC 处理速度很快(1 秒内)。

  • Full GC 不经常执行(每 10 分钟一次)。


括号内的数值不是绝对值,它们因服务状态而异,有些服务可能会满足 0.9 秒的 Full GC 处理速度,但有些服务可能不会,因此请检查这些值并通过考虑每个服务的实际情况来决定是否执行 GC 调整。


当检查 GC 状态时应该注意一件事,不要只检查 Minor GC 和 Full GC 的时间,还必须检查 GC 执行次数,如果 New area size 太小,会导致 Minor GC 执行过于频繁(有时每 1 秒一次或多次),此外传递到 Old 区的对象数量增加,导致 Full GC 执行次数增加,因此应用jstat –gccapacity 命令中的选项来检查该区域被占用了多少。


设置 GC 类型/内存大小



设置 GC 类型

Oracle JVM 有五种 GC 类型,但是如果不是 JDK 7,则应选择 Parallel GCParallel Compacting GCCMS GC 之一,没有原则或规则来决定选择哪一个。


如果是这样,我们如何选择合适的 GC 类型? 最推荐的方法是应用所有三个。然而,有一点很清楚——CMS GC 比其他并行 GC 更快。这时候,如果是这样,只需应用 CMS GC。然而 CMS GC 并不总是更快,通常 CMS GC 的 Full GC 速度很快,但是当发生并发模式失败时,它比其他 Parallel GC 慢。


并发模式失败


让我们更深入地研究并发模式失败。


Parallel GCCMS GC 最大的区别在于压缩任务,压缩任务是通过压缩内存来消除内存碎片,以去除分配的内存区域之间的空白空间。


在 Parallel GC 类型中,每次执行 Full GC 时都会执行压缩,花费太多时间。但是在执行 Full GC 后,内存可以以更快的方式分配,因为可以顺序分配下一个内存。


相反,CMS GC 不伴随压缩,因此 CMS GC 的执行速度更快。但是,当不执行 compaction 时,内存中会产生一些空白空间,就像执行磁盘碎片整理程序之前一样,可能没有空间容纳大对象。例如 Old 区还剩 300 MB,但有些 10 MB 的对象无法依次保存在该区中,因为没有连续的 10 MB 的内存空间,在这种情况下会出现 并发模式失败 警告并执行压缩。如果使用 CMS GC,则执行压缩比其他 Parallel GC 需要更长的时间


总之,你应该为你的系统找到最佳的 GC 类型。


每个系统都需要合适的 GC 类型,因此你需要为你的系统找到最佳的 GC 类型。如果你正在运行六台服务器,我建议你为两台服务器中的每台设置相同的选项,添加-verbosegc选项,然后分析结果。

设置内存大小

下面展示了内存大小、GC 执行次数、GC 执行时间之间的关系:


  • 大内存

  • 减少 GC 执行的次数。

  • 增加 GC 执行时间。

  • 小内存

  • 减少 GC 执行时间。

  • 增加 GC 执行的次数。


将内存大小设置为多大没有正确的答案。如果是这样,我们应该如何设置内存大小? 通常我建议使用 500 MB。但请注意,这并不意味着你应该使用–Xms500m–Xmx500m选项设置 web 应用的内存。根据 GC 调优前的当前状态,查看 Full GC 后剩余的内存大小。如果 Full GC 后还剩 300MB 左右,最好将内存设置为 1GB(300MB(默认使用)+500MB(Old 区最小)+200MB(空闲内存)),这意味着你应该为 Old 区域设置超过 500 MB 的内存空间。


如果你有三台操作服务器,请将一台服务器设置为 1 GB,一台设置为 1.5 GB,一台设置为 2 GB,然后查看结果。理论上,GC 将按照 1 GB > 1.5 GB > 2 GB 的顺序快速完成,因此 1 GB 将是执行 GC 的最快速度。但是不能保证在 1 GB 时执行 Full GC 需要 1 秒,在 2 GB 时执行 Full GC 需要 2 秒。时间取决于服务器性能和对象大小,因此创建测量数据的最佳方法是设置尽可能多的数据并对其进行监控。


你应该再设置一个参数:NewRatioNewRatio是 New 区和 Old 区的比值。如果XX:NewRatio=1New 区:Old 区是 1:1,对于 1 GB,新区:旧区为 500MB:500MB。如果NewRatio是 2,则 New 区:Old 区是 1:2,因此该值越大,Old 区域大小越大,New 区域大小越小。


NewRatio 会显着影响整个 GC 性能,如果 New area 内存太小,就会有很多内存分配给 Old area,导致 Full GC 频繁,处理时间长。


你可能只是认为那NewRatio 1 是最好的,然而事实可能并非如此,当NewRatio设置为 2 或 3 时,整个 GC 状态可能会更好。


完成 GC 调优的最快方法是什么? 比较性能测试的结果是获得结果的最快方法。要为每个服务器设置不同的选项并监控状态,建议至少在一两天后检查数据。但是,通过性能测试执行 GC 调优时,你应该准备好与操作情况相同的负载。并且提供负载的 URL 等请求比例必须与操作情况一致。但是,对于专业的性能测试人员来说,给出准确的负载并不容易,而且准备时间太长。


分析 GC 调优结果




应用 GC 选项并设置该 -verbosegc 选项后,使用tail命令检查日志是否按需要累积。如果该选项没有准确设置并且没有累积日志,你将浪费你的时间。如果日志按需要累积,请在收集数据一两天后查看结果。最简单的方法是将日志移动到本地 PC 并使用 HPJMeter 分析数据。


在分析中,重点关注以下内容,决定 GC 选项的最重要的一项是 Full GC 执行时间。


  • Full GC 执行时间

  • Minor GC 执行时间

  • Full GC 执行间隔

  • Minor GC 执行间隔

  • 整个 Full GC 执行时间

  • 整个 Minor GC 执行时间

  • 整个 GC 执行时间

  • Full GC 执行次数

  • Minor GC 执行次数


找到最合适的 GC 选项是一个非常幸运的,而在大多数情况下,事实并非如此。执行 GC 调优时要小心,因为如果你尝试一次完成 GC 调优,可能会发生OutOfMemoryError这种情况。


调优示例




到目前为止,我们已经在没有任何示例的情况下从理论上讨论了 GC 调优。现在我们来看看 GC 调优的例子。

示例 1

以下示例是 Service S 的 GC 调整。对于新开发的 Service S,执行 Full GC 花费了太多时间。


查看结果jstat –gcutil


S0 S1 E O P YGC YGCT FGC FGCT GCT


12.16 0.00 5.18 63.78 20.32 54 2.047 5 6.946 8.993


复制代码


执行 Minor GC 和 Full GC 一次的平均值计算如下:


表 3:为服务 S 执行 Minor GC 和 Full GC 所需的平均时间。


| GC 类型 | GC 执行时间 | GC 执行时间 | 平均数 |


| --- | --- | --- | --- |


| Minor GC | 54 | 2.047 | 37 毫秒 |


| Full GC | 5 | 6.946 | 1,389 毫秒 |


37 ms 对于 Minor GC 来说还不错。但是 Full GC 的 1.389 秒 意味着在 DB Timeout 设置为 1 秒的系统中发生 GC 时,可能会频繁发生超时,在这种情况下,系统需要 GC 调优。


首先,你应该在开始 GC 调整之前检查内存是如何使用的,使用该 jstat –gccapacity 选项检查内存使用情况。从该服务器检查的结果如下。


NGCMN NGCMX NGC S0C S1C EC OGCMN OGCMX OGC OC PGCMN PGCMX PGC PC YGC FGC


212992.0 212992.0 212992.0 21248.0 21248.0 170496.0 1884160.0 1884160.0 1884160.0 1884160.0 262144.0 262144.0 262144.0 262144.0 54 5


复制代码


关键值如下。


  • New 区使用大小:212,992 KB

  • Old 区使用大小:1,884,160 KB


因此,总分配的内存大小为 2 GB,不包括 Perm 区域,New area:Old area 为 1:9。为了以比 jstat 更详细的方式检查状态,-verbosegc 添加了日志并为三个实例设置了三个选项,如下所示。没有添加其他选项。


  • NewRatio=2

  • NewRatio=3

  • NewRatio=4

评论

发布
暂无评论
如何调优 Java 垃圾收集