几个虚拟机的垃圾收集

时间:2016-04-18 10:31:22

标签: java memory garbage-collection jvm jvm-hotspot

我们有一个通常由~20个JVM组成的应用程序,我们将批处理作业分发给它们。 20个JVM在同一操作系统中运行。在将批处理作业发送给其中一个作业之前,很难判断作业的长度和大小。可能需要1分钟或几个小时。内存消耗也同样不同。

到目前为止,这种方法运行良好,我们总共有40GB可用内存,每个JVM的最大堆大小设置为2GB(有时需要2GB)。因为我们从来没有过多的"大"批处理作业同时运行,我们从未遇到过内存问题。直到我们转移到Java 8 vm。似乎不太频繁地触发完整的GC。我们让JVM在内存使用方面大部分空闲。当我通过调用jcmd触发GC时,我可以看到OldGen从1GB下降到200MB。

我知道这不是一个很好的设置,有20个JVM,最大2GB Heap + Stack + Metaspace最多可以比40GB可用内存多得多。但这是我们必须忍受的情况。如果有一种方法可以为多个JVM集群设置最大堆大小,我会感到惊讶。所以我需要提出其他解决方案。

我正在寻找一些VM选项,告诉VM定期执行完整的GC,这很可能解决了我们的问题。但我无法找到VM选项来执行此操作。

有关我们如何设置以避免内存交换的任何建议?

编辑:这是gc日志中的一个片段:

2016-04-14T01:02:49.413+0200: 37428.762: [Full GC (Ergonomics) [PSYoungGen: 28612K->0K(629248K)] [ParOldGen: 1268473K->243392K(1309184K)] 1297086K->243392K(1938432K), [Metaspace: 120332K->120320K(1181696K)], 0.3438924 secs] [Times: user=1.69 sys=0.02, real=0.35 secs] 
2016-04-14T01:02:52.442+0200: 37431.792: [GC (Allocation Failure) [PSYoungGen: 561664K->67304K(629248K)] 805056K->310696K(1938432K), 0.0315138 secs] [Times: user=0.26 sys=0.00, real=0.03 secs] 
2016-04-14T01:02:54.809+0200: 37434.159: [GC (Allocation Failure) [PSYoungGen: 628968K->38733K(623104K)] 872360K->309555K(1932288K), 0.0425780 secs] [Times: user=0.35 sys=0.00, real=0.04 secs] 
...
2016-04-14T10:09:03.558+0200: 70202.907: [GC (Allocation Failure) [PSYoungGen: 547152K->41386K(531968K)] 1545772K->1041036K(1841152K), 0.0255883 secs] [Times: user=0.18 sys=0.00, real=0.02 secs] 
2016-04-14T10:20:53.634+0200: 70912.984: [GC (Allocation Failure) [PSYoungGen: 531882K->40733K(542720K)] 1531532K->1042107K(1851904K), 0.0306816 secs] [Times: user=0.22 sys=0.02, real=0.03 secs] 
2016-04-14T10:23:10.830+0200: 71050.180: [GC (System.gc()) [PSYoungGen: 60415K->37236K(520192K)] 1061790K->1040674K(1829376K), 0.0228505 secs] [Times: user=0.17 sys=0.01, real=0.02 secs] 
2016-04-14T10:23:10.853+0200: 71050.203: [Full GC (System.gc()) [PSYoungGen: 37236K->0K(520192K)] [ParOldGen: 1003438K->170089K(1309184K)] 1040674K->170089K(1829376K), [Metaspace: 133559K->129636K(1196032K)], 1.4149811 secs] [Times: user=11.10 sys=0.02, real=1.42 secs] 

如果我们每小时都有一个完整的GC,我猜它会解决我们的问题。

3 个答案:

答案 0 :(得分:2)

在随机时间进行GC没有意义。

我会将GC添加到批处理的末尾(或之后)。在这一点上,可能需要保留最少的内存,使GC更快,并获得最佳收缩。

答案 1 :(得分:1)

您可以尝试使用-XX:GCTimeRatio=14 -XX:MaxHeapFreeRatio=30 -XX:MixHeapFreeRatio=20运行,而不是尝试使用时间触发的GC。这将告诉收集器保持较少的空间,并允许它更频繁地收集/在GC上花费更多的CPU周期。

在当前的JDK9版本中,这可以进一步与-XX:-ShrinkHeapInSteps结合使用,以使分配的堆大小更加紧密地跟踪使用的堆。同样,可能会牺牲性能。

答案 2 :(得分:0)

感谢所有回答/评论。 我想出的解决方案是大量答案/评论的组合。

@Peter Lawrey: 每次批量运行后调用System.gc()都很有意义,我很惊讶我们之前没有提出这个问题。它本身并没有帮助缩小内存使用量。我们最终会得到一个只有200MB数据的1GB旧代。

@ the8472: GCTimeRatio似乎没有以任何方式帮助我们。但我们将MaxHeapFreeRatio和MinHeapFreeRatio都更改为40.选择较低的值会限制Young Generation的大小,并且它从未超过200MB。 我假设将两个参数设置为相同的值将导致大量内存分配和解除分配,但我们仍然在GC中花费的时间<1%仍然表现良好。当您执行大量数据库请求时,GC的性能影响变得可以忽略: - )

@Sisyphus: 将newRatio设置为1有助于让Young Generation和Old Generation具有相似的尺寸。这可能是最大利益的变化。