【问题标题】:How to deal with long Full Garbage Collection cycle in Java如何在 Java 中处理较长的完整垃圾回收周期
【发布时间】:2019-05-01 15:28:06
【问题描述】:

我们继承了一个在生产中运行的系统,最近每 10 小时就开始出现故障。基本上,如果系统一分钟无响应,我们的内部软件就会标记出故障的系统。我们发现我们的问题是我们的 Full GC 周期持续了 1.5 分钟,我们使用了 30 GB 堆。现在的问题是,我们无法在短时间内进行很多优化,也无法快速划分服务,但我们需要尽快摆脱 1.5 分钟的停顿,因为生产中的这些停顿导致我们的系统出现故障。对我们来说,可接受的延迟是 20 毫秒,但不会更多。调整系统的最快方法是什么?减少堆以频繁触发 GC?使用 System.gc() 提示?还有其他解决方案吗?我们使用 Java 8 默认设置,并且我们拥有越来越多的用户 - 即创建的对象越来越多。

一些 GC 统计数据

【问题讨论】:

  • 如果您的 JVM 支持,请尝试其他 GC 算法之一。
  • Full GC 真的能成功释放很多空间吗?

标签: java garbage-collection jvm


【解决方案1】:

您有很多保留的数据。有几个选项值得考虑。

  • 将堆增加到 32 GB,如果您有空闲内存,这几乎没有影响。再次查看您的总数,您似乎使用的是 32 GB 而不是 30 GB,因此这可能无济于事。
  • 如果您没有足够的可用内存,则可能会交换一小部分堆,因为这会显着增加完整的 GC 时间。
  • 可能有一些简单的方法可以使数据结构更紧凑。例如使用紧凑的字符串,使用原语而不是包装器,例如long 用于时间戳,而不是 DateLocalDateTime。 (long 大约是大小的 1/8)
  • 如果这些都没有帮助,请尝试将一些数据移出堆。例如Chronicle Map 是一个 ConcurrentMap,它使用堆外内存可以显着减少你的 GC 时间。即,堆外存储的数据没有 GC 开销。添加的难易程度取决于您的数据结构。

我建议分析您的数据的结构,看看是否有任何简单的方法可以提高效率。

【讨论】:

  • 谢谢彼得,没明白为什么堆增加影响不大?你能提供详细的答案吗?因为,这是我们与其他人相比最快的解决方案。
  • @Mark 使用 30 GB 或 32 GB 堆不会减慢 JVM,前提是有可用内存。相比之下,33 GB 堆会更慢,因为它必须使用 64 位引用。拥有更大的堆将减少 GC 之间的时间,而更大的年轻空间可以减少过早提升的数量。
  • @Mark 在任何情况下,您都应该考虑通过小的更改可以显着减少内存使用量,尤其是如果过去没有考虑过这一点。您需要分析内存使用情况,例如用飞行记录仪进行评估。大部分工作是调查而不是更改代码。但是,您可以看到在许多情况下显着减少。
【解决方案2】:

没有万能的灵丹妙药解决您的问题:您需要很好地处理应用程序的分配和活动模式,并且您需要知道它如何与特定的您正在运行的垃圾收集算法(Java 版本的功能和传递给java 的命令行标志)。

从广义上讲,Full GC(成功回收大量空间)意味着大量对象在次要收集中幸存下来(但没有被泄漏)。首先查看您的 Eden 和 Survivor 空间的大小:如果 Eden 太小,次要收集将非常频繁地运行,并且您可能没有给对象在达到其寿命阈值之前死亡的机会。如果 Survivor 太小,对象会过早地被提升到 Old gen。

GC 调优是一门艺术:你运行你的应用程序,研究结果,调整一些参数,然后再次运行它。因此,您将需要应用程序的基准版本,它的行为尽可能接近生产版本,但希望不需要 10 小时即可导致完整的 GC。

正如您所说,您正在使用默认设置运行 Java 8,我相信这意味着您的旧集合正在使用串行收集器运行。通过切换到老年代的并行收集器(-XX:+UseParallelOldGC),您可能会看到一些非常快速的改进。虽然这可能会将 1.5 分钟的暂停时间减少到几秒(取决于您的机器上的核心数量,以及您为 GC 指定的线程数),但这不会将您的最大暂停时间减少到 20 毫秒。

【讨论】:

  • @Mark - CMS 可能会更快,也可能不会。 “最佳” GC 高度依赖于您的特定应用程序和工作负载(以及“最佳”在您的上下文中的含义)。唯一的方法就是测试。
  • 谢谢,这是我们的 GC 日志 65154.265:[完整 GC(人体工程学)[PSYoungGen: 2765438K->0K(8924160K)] [ParOldGen: 24328402K->22508332K(24466944K)] 2709382K1K->33 33391104K), [元空间: 78790K->78163K(83968K)], 80.8081759 secs] [Times: user=147.73 sys=0.68, real=80.81 secs] 我想我们已经在使用 Parallel Collector
  • @Mark - 文本 "24 328 402K->22 508 332K" 表示 GC 实际上并没有成功收集旧代中的大部分空间:已用空间的大小老一代从 24 场演出减少到 22 场演出。这要么意味着您合法地耗尽了内存,要么意味着某处存在泄漏。没有 GC 算法或调优可以解决这个问题!
  • @Mark - 有一个权衡,你提供的内存越多,GC 发生的时间越长,但 GC 运行的时间越长。如果您有一个“重新启动窗口”,并且可以为应用程序提供足够的内存以使其进入该重新启动窗口,那么这可能非常有用。否则,您需要弄清楚是否存在泄漏(并解决它),或者弄清楚如何使用更少的内存。一个很好的第一个工具是“jmap”实用程序 - 您可以使用它来转储堆上所有活动对象的直方图。
  • 请更新您的帖子以包含 cmets 中提到的其他详细信息
【解决方案3】:

当这种情况发生在我身上时,是由于静态变量占用内存导致的内存泄漏。我会检查所有最近的代码更改并寻找任何可能的内存泄漏。

【讨论】:

  • 我们进行了分析,但没有泄漏。我们只是流量增长,产生了更多的垃圾
  • 也有可能是服务器配置问题,但是 IDK 你的服务器是什么样的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-03-21
  • 1970-01-01
  • 2014-03-09
  • 1970-01-01
  • 2013-05-16
  • 1970-01-01
相关资源
最近更新 更多