【问题标题】:Java Garbage Collector time limitJava 垃圾收集器时间限制
【发布时间】:2018-02-26 13:18:52
【问题描述】:

情况

我正在为 Java 竞赛开发一个客户端,每当我收到请求时,我都有两秒钟的时间做出响应。响应后到下一个请求的时间未知。

有时,找到正确的响应需要将近 2 秒,有时只需几毫秒。问题是当垃圾回收发生在一个较长的计算(也分配了很多对象)中,就在两秒快结束时,因此响应发送得太晚,我被取消资格。

使用详细的 gc 输出,我发现 gc 通常需要大约 0.6 秒,即使我试图将其限制得更低。我还尝试在较短的计算中调用System.gc()(因为我确信我有大约 1.8 秒的时间不需要做任何事情),但它需要 1-3 秒,这也不安全。

我的程序只有很少的长寿命对象,大多数寿命短于一秒。

规格

我知道程序将始终在具有这些可用资源的同一台机器上运行:

我当前的jvm参数:

java -Dfile.encoding=UTF-8 \
  -XX:MaxGCPauseMillis=200 \
  -XX:GCPauseIntervalMillis=2050 \
  -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled \
  -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70 \
  -XX:+ScavengeBeforeFullGC -XX:+CMSScavengeBeforeRemark \
  -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps

想法

  • 我能否以某种方式告诉 gc 它现在应该收集一些垃圾,但只收集大约 1.5 秒?
  • 是否有一个 System.gc() 等效项仅适用于年轻对象并且不检查终身代?
  • 能否优化 jvm 参数以获得更好的效果?

【问题讨论】:

  • 据我所知,你不能真正限制垃圾收集。您只能尝试在计算时保留对所有变量的引用,因为这样 GC 肯定不会运行(因为所有内容仍然被引用,因此无法释放)。然后在计算之后取消引用所有内容,以便 gc 可以运行(并删除 System.gc() 以希望您的垃圾收集得到提示)
  • 与其尝试优化 Java 垃圾收集(这类似于说您想对哥斯拉进行化妆),不如尝试专注于更大的问题。为什么每次收到请求时,Java 都需要执行 GC? GC 确实发生了很多,但如果你找到了一个模式,那么它就是你的程序导致它发生。你可能想好好看看它背后的原因。
  • 您是否尝试过使用 G1 垃圾回收,它有更频繁的微循环,几乎无法察觉。 System.gc() 只是一个建议,在现代收集器中并没有做任何明显的事情。将上面所有的 GC 参数替换为:--XX:+UseG1GC
  • 一种不好的方法是调用 GC,我的朋友也有同样的问题,即对象管理不善,调用 GC 或 GC 做了很多工作。所以他每次都用这个参数运行 GC:- java -Dsun.rmi.dgc.client.gcInterval=3600000 但我认为这也是一个不好的方法。看看你的程序或阅读这个GC approach
  • 学习c++的时间

标签: java garbage-collection jvm


【解决方案1】:

您可以尝试减少停顿:

  • 尝试使用 G1 收集器而不是 CMS
  • 使用更现实的 GC 目标。当前目标允许每 2 秒 200 毫秒的 stop-the-world GC 时间。增加 MaxGCPauseMillis 和/或减少 GCPauseIntervalMillis。
  • 添加另一个内核,以便 JVM 能够与您的应用程序并行进行 GC。
  • 减少 CMSInitiatingOccupancyFraction 以便 GC 在堆不太满时触发后台 GC 线程。
  • 降低应用程序产生垃圾的速度。
  • 调整您的应用程序算法,以便有更多空闲时间用于后台 GC(在您的单核上)。

【讨论】:

  • “添加另一个核心”比赛系统不在我的控制之下。 “降低您的应用程序产生垃圾的速度。”我必须不断地为 BFS 克隆对象,所以这也不是一个选择。我认为 G1 已针对性能更高的环境进行了优化,但目前看起来还不错。我唯一想知道的是为什么它完全没有记录任何活动,尽管我把-verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps 留在了那里。
  • “我必须不断地为 BFS 克隆对象,所以这也不是一个选择。” - 广度优先搜索不需要任何克隆。现在,您的“持续克隆”可能是一种方便的方式来做……某事……但如果您重新设计/重新编码您的算法,您很可能可以避免它。这是一种选择,虽然可能不是一个有吸引力的选择。 (这取决于你想赢得多少比赛:-))
  • 另一个问题:这两个现代 GC 似乎总是与多个工人一起工作,考虑到我只有一个核心,这听起来效率不高。所以我希望我的 GC 是 concurrent,而不是 parallel,这可能吗?
  • 基本上没有。 JVM 是多线程的,GC 使用线程来运行 GC 工作者。如果您没有多个内核,那么线程将由操作系统进行时间分片。
【解决方案2】:

经过大量试验,我找到了我需要的标志,现在想分享这些知识:

  • -XX:+UseConcMarkSweepGC - 在比较我的用例的 G1 和 CMS 的日志结果后,我确定 t CMS 暂停时间更低,此外它仍然支持单线程集合
  • -XX:+ExplicitGCInvokesConcurrent 在调用 System.gc() 时不会调用 Full GC,而是调用标准 GC。
  • -XX:NewRatio=1老年代与年轻代的大小比,这是最低的值,因为我的长寿命对象很少
  • -mx800m -ms800m 减少并修复了内存大小,因此收集将更频繁地发生并且花费更少的时间。以吞吐量换取响应能力。
  • -XX:-UseParNewGC 禁用并行化以收集年轻代。这是交易破坏者,并将 GC 停止世界的时间从 0.2-0.5 秒减少到 0.02-0.2 秒,因为我只有一个内核可用。 (不推荐将其与 CMS 结合使用,并且可能会在较新的 Java 版本中被删除)

使用这些参数,我可以将 GC 暂停时间从 0.4 秒到 3 秒之间的变化减少到始终小于 0.2 秒。为了完整起见,这些标志对调试最有用:

-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution

请不要忘记,这些是针对非常具体的要求进行了优化的:只有一个内核可用、大量年轻垃圾、没有旧收集、非常低的 stop-the-world-pauses

这是一个小备忘单,我可以推荐进一步阅读:http://blog.ragozin.info/2016/10/hotspot-jvm-garbage-collection-options.html

【讨论】:

    猜你喜欢
    • 2011-11-07
    • 1970-01-01
    • 1970-01-01
    • 2010-12-13
    • 1970-01-01
    • 2012-06-28
    • 2011-01-06
    • 2010-10-04
    • 1970-01-01
    相关资源
    最近更新 更多