【发布时间】:2020-11-10 01:51:30
【问题描述】:
我有一项服务,它从源读取数据,对数据执行一些转换,然后将转换后的数据上传到目标。在选择 GC 算法时,我正在寻找具有高吞吐量的算法,这就是我选择并行 GC 的原因。让我很困惑的部分是为什么我看到了大量的 Full GC。服务的性质使大多数对象随着数据的来来去去而短暂存在。这是我的 GC 配置:
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintTenuringDistribution -verbose:GC -XX:+UseParallelGC -XX:NewSize=21200m -XX:MaxNewSize=21200m -server -Xms31200m -Xmx31200m
基本上,我将总堆大小设置为 30GB,将新生代大小设置为 20GB。
这是一段 GC 日志:
2020-11-08T08:31:07.863+0000: 215.876: [Full GC (Ergonomics) [PSYoungGen: 1233347K->0K(18729472K)] [ParOldGen: 9065862K->6633660K(10240000K)] 10299209K->6633660K(28969472K), [Metaspace: 107588K->107588K(1144832K)], 1.1350824 secs] [Times: user=21.03 sys=0.00, real=1.14 secs]
2020-11-08T08:31:10.627+0000: 218.640: [GC (GCLocker Initiated GC)
Desired survivor size 2699034624 bytes, new threshold 1 (max 15)
[PSYoungGen: 15874560K->1274938K(19073024K)] 22513996K->7914375K(29313024K), 0.1073842 secs] [Times: user=3.10 sys=0.00, real=0.11 secs]
2020-11-08T08:31:12.319+0000: 220.331: [GC (GCLocker Initiated GC)
Desired survivor size 2587885568 bytes, new threshold 1 (max 15)
[PSYoungGen: 17602106K->1307000K(18962944K)] 24253865K->8618788K(29202944K), 0.2492961 secs] [Times: user=7.16 sys=0.00, real=0.25 secs]
2020-11-08T08:31:14.197+0000: 222.210: [GC (GCLocker Initiated GC)
Desired survivor size 2480930816 bytes, new threshold 1 (max 15)
[PSYoungGen: 17634168K->1333816K(19286016K)] 24952891K->9297010K(29526016K), 0.2524904 secs] [Times: user=7.07 sys=0.00, real=0.25 secs]
2020-11-08T08:31:16.165+0000: 224.178: [GC (GCLocker Initiated GC)
Desired survivor size 2386558976 bytes, new threshold 1 (max 15)
[PSYoungGen: 18092600K->1313137K(19181568K)] 26062932K->9992006K(29421568K), 0.2845171 secs] [Times: user=7.85 sys=0.00, real=0.29 secs]
2020-11-08T08:31:18.084+0000: 226.096: [GC (GCLocker Initiated GC)
Desired survivor size 2312110080 bytes, new threshold 1 (max 15)
[PSYoungGen: 18071921K->1242981K(19450880K)] 26751020K->10584632K(29690880K), 0.2523254 secs] [Times: user=6.79 sys=0.00, real=0.26 secs]
2020-11-08T08:31:18.336+0000: 226.349: [Full GC (Ergonomics) [PSYoungGen: 1242981K->0K(19450880K)] [ParOldGen: 9341651K->6896991K(10240000K)] 10584632K->6896991K(29690880K), [Metaspace: 107625K->107625K(1144832K)], 1.0198299 secs] [Times: user=18.34 sys=0.08, real=1.02 secs]
2020-11-08T08:31:21.049+0000: 229.062: [GC (GCLocker Initiated GC)
Desired survivor size 2221408256 bytes, new threshold 1 (max 15)
[PSYoungGen: 17120256K->1356565K(19378176K)] 24043241K->8279559K(29618176K), 0.1089915 secs] [Times: user=3.38 sys=0.00, real=0.11 secs]
2020-11-08T08:31:22.887+0000: 230.899: [GC (GCLocker Initiated GC)
Desired survivor size 2155872256 bytes, new threshold 1 (max 15)
[PSYoungGen: 18476821K->1265473K(19603456K)] 25426058K->8896652K(29843456K), 0.2524566 secs] [Times: user=7.14 sys=0.00, real=0.25 secs]
2020-11-08T08:31:24.888+0000: 232.901: [GC (GCLocker Initiated GC)
Desired survivor size 2092433408 bytes, new threshold 1 (max 15)
[PSYoungGen: 18699585K->1388375K(19539456K)] 26345045K->9562491K(29779456K), 0.2113546 secs] [Times: user=5.59 sys=0.00, real=0.21 secs]
2020-11-08T08:31:26.819+0000: 234.832: [GC (GCLocker Initiated GC)
Desired survivor size 2030043136 bytes, new threshold 1 (max 15)
[PSYoungGen: 18822487K->1308016K(19726336K)] 27003840K->10002863K(29966336K), 0.2078162 secs] [Times: user=6.10 sys=0.00, real=0.21 secs]
2020-11-08T08:31:28.868+0000: 236.881: [GC (GCLocker Initiated GC)
Desired survivor size 2030043136 bytes, new threshold 1 (max 15)
[PSYoungGen: 18990960K->1521040K(19665408K)] 27712283K->10780549K(29905408K), 0.2373748 secs] [Times: user=6.60 sys=0.00, real=0.23 secs]
2020-11-08T08:31:29.106+0000: 237.119: [Full GC (Ergonomics) [PSYoungGen: 1521040K->0K(19665408K)] [ParOldGen: 9259509K->7378423K(10240000K)] 10780549K->7378423K(29905408K), [Metaspace: 107653K->107653K(1144832K)], 1.0809680 secs] [Times: user=20.55 sys=0.00, real=1.09 secs]
日志中有几件事让我很困惑:
- JVM 如何计算所需的幸存者大小?为什么它大约是 2.5 GB?为什么每次软 GC 都会有一点点变化?为什么老一代的总大小从未改变(10240000K),而年轻一代的总大小却一直在变化?
- 为什么 *新阈值 始终为 1?这不是太激进了,不能将东西转移到老一代吗?
- 在每次软 GC 之后,young gen 很可能有大约 1.3GB 的数据,并且一些数据被移动到 old gen。这导致 old gen 逐渐变满,Full GC 最终碰巧清理了 old gen。为什么每次软 GC 都会有一部分数据被移到老年代?看起来幸存者空间足够大。
- 如何避免不必要的 Full GC,从而提高整体吞吐量?
【问题讨论】:
-
我将只解决第 4 点,因为下面的答案涉及前 3 点。您选择了一个 GC,它在真正需要之前不会执行 old gen(或 full)gc。 Full gc 是最昂贵的,cpu 周期专门用于您的工作。使用并发 gc:s 将删除部分或全部完整 gc:s 但您将失去 cpu 周期。因此,不能保证并发 gc 实际上会更快。
-
几件事: 1. 我看到即使使用 Full GC,JVM 也无法从老年代清除太多空间。这表示长引用的对象。看起来您的应用程序正在保持 ~6.5GB 对象“活动”。然后我注意到你为老一代留下了大约 10GB。我认为这是一个非常紧张的情况。我不确定你为什么要设置一个巨大的年轻一代,它是老一代的两倍。这可能是您经常看到 Full GC 的原因。 2. 根据这篇博客[blogs.oracle.com/poonam/…,Parallel GC不使用年龄表。
-
contd...所以我认为在这种情况下对象年龄并不重要。当你在探索不同的 GC 算法时,我建议从基础开始。首先,检查“开箱即用”不同 GC 策略的执行情况。之后,您可以开始从很多中调整速度更快的那个。在这种情况下,我建议删除那些 NewSize 参数。您还可以添加 -XX:+UseParallelOldGC 以使用并行线程更快地收集旧代。
-
还可以使用 -XX:+PrintAdaptiveSizePolicy 来获取年轻代中存活的数据量以及每次年轻 GC 提升到老年代的数据量信息。它将帮助您进一步调查。
-
@suv3ndu “看起来您的应用程序正在保持约 6.5GB 对象“活动”,这是一个很好的观点,这绝对是出乎意料的。应用程序从一个源读取数据,对数据进行一些转换,然后然后将它们上传到目的地。内存占用不应该是这样的。我需要弄清楚为什么有些对象的寿命比预期的要长。