【问题标题】:Garbage Collection for several VMs多个 VM 的垃圾收集
【发布时间】:2016-08-10 00:59:53
【问题描述】:

我们有一个通常由大约 20 个 JVM 组成的应用程序,我们将批处理作业分配给它们。 20 个 JVM 在同一个操作系统中运行。在将批处理作业分派给其中一个之前,很难判断该作业需要多长时间和多大。这可能需要 1 分钟或几个小时。内存消耗也有类似的变化。

到目前为止,这运行良好,我们总共有 40GB 可用内存,我们将每个 JVM 的最大堆大小设置为 2GB(有时需要 2GB)。因为我们从来没有同时运行过多的“大”批处理作业,所以我们从来没有遇到过内存问题。直到我们迁移到 Java 8 vm。似乎完全 GC 的触发频率较低。我们的 JVM 在内存使用量方面大多处于空闲状态。当我通过调用 jcmd 触发 GC 时,我可以看到 OldGen 从 1GB 下降到 200MB。

我知道这不是一个好的设置,拥有 20 个 JVM,最大 2GB 堆 + 堆栈 + 元空间,这将远远超过可用的 40GB 内存。但这是我们必须忍受的情况。如果有办法为多个 JVM 的集群设置最大堆大小,我会感到惊讶。所以我需要想出其他的解决方案。

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

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

编辑:这是 gc 日志中的一个 sn-p:

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,我想它会解决我们的问题。

【问题讨论】:

  • System.gc() ?但要注意:它根本不能保证做任何事情,通常称之为不是解决方案。此外:对象确实会老化,我认为非常古老的对象会永远留在内存中......
  • 为什么你需要强制进行一次完整的 GC,而不是让 G1 收集器在它决定需要的时候进行一次完整的 GC?你已经告诉它它可以使用 2GB 的堆,并且你试图在它只使用 1GB 时触发一个完整的 GC。这似乎很奇怪。您实际上可能想考虑调整您的 JVM,以便在老一代中减少对象 - 考虑到您的使用模式,我猜您的大型、长期存在的批次正在填充老一代,而您可能宁愿他们没有.
  • @Exceptyon 不,他们肯定不会。无论年龄大小,都会收集无法访问的对象。
  • @Sisyphus 我认为这是问题的一部分。我想我可以使用 300MB 的老一代,但我需要高达 1.5GB+ 的年轻一代。但我不认为我可以配置比老一代更大的年轻一代。我想我会试试 newRatio=1

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


【解决方案1】:

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

在当前的 JDK9 版本中,这可以进一步与 -XX:-ShrinkHeapInSteps 结合,让分配的堆大小更接近使用的堆。同样,可能会以牺牲性能为代价。

【讨论】:

  • 到目前为止,我对 GCTimeRatio 的理解是,这应该会限制花费在 GC 上的时间。我真的可以使用这个选项来告诉虚拟机花更多的时间在 GC 上吗?
  • 不完全是,但收集器有一个隐含的足迹目标,只有在暂停时间和时间比率约束之后才会考虑。由于并行收集器在默认情况下没有配置暂停时间目标,因此放宽时间比率约束将允许它更积极地满足其吞吐量目标。结合其他可能导致更频繁收集的选项。
【解决方案2】:

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

@彼得劳里: 在每次 Batch 运行后调用 System.gc() 很有意义,我很惊讶我们之前没有提出这个问题。它本身并没有帮助减少内存使用量。我们最终会得到一个只有 200MB 数据的 1GB 老一代。

@the8472: GCTimeRatio 似乎对我们没有任何帮助。但是我们将 MaxHeapFreeRatio 和 MinHeapFreeRatio 都更改为 40。选择较低的值会过多地限制年轻代的大小,并且它永远不会超过 200MB。 我假设将两个参数设置为相同的值会导致大量的内存分配和释放,但我们仍然做得很好,在 GC 中花费的时间不到 1%。当您执行大量数据库请求时,GC 的性能影响变得可以忽略 :-)

@西西弗斯: 将 newRatio 设置为 1 有助于让年轻一代和老一代拥有相似的大小。这可能是收益最高的变革。

【讨论】:

  • 现在我们让它运行了一段时间:将 newRatio 设置为 1 是一个糟糕的主意。使用 2GB 堆,老一代永远不会超过 1GB。当老一代的 1GB 全部用完但新一代仍有 600+MB 的空闲内存时,我们会用完内存。
【解决方案3】:

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

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

【讨论】:

    猜你喜欢
    • 2011-02-18
    • 2011-01-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-30
    • 1970-01-01
    • 1970-01-01
    • 2010-12-13
    相关资源
    最近更新 更多