【问题标题】:Reducing JVM pause time > 1 second using UseConcMarkSweepGC使用 UseConcMarkSweepGC 减少 JVM 暂停时间 > 1 秒
【发布时间】:2010-10-09 02:38:35
【问题描述】:

我正在一台具有 16Gb RAM、一个 8 核处理器和 Java 1.6 的机器上运行一个内存密集型应用程序,所有这些都在 CentOS 版本 5.2(最终版)上运行。确切的 JVM 详细信息是:

java version "1.6.0_10"
Java(TM) SE Runtime Environment (build 1.6.0_10-b33)
Java HotSpot(TM) 64-Bit Server VM (build 11.0-b15, mixed mode)

我正在使用以下命令行选项启动应用程序:

java -XX:+UseConcMarkSweepGC -verbose:gc -server -Xmx10g -Xms10g ...

我的应用程序公开了一个 JSON-RPC API,我的目标是在 25 毫秒内响应请求。不幸的是,我看到延迟达到并超过 1 秒,这似乎是由垃圾收集引起的。以下是一些较长的示例:

[GC 4592788K->4462162K(10468736K), 1.3606660 secs]
[GC 5881547K->5768559K(10468736K), 1.2559860 secs]
[GC 6045823K->5914115K(10468736K), 1.3250050 secs]

这些垃圾收集事件中的每一个都伴随着延迟的 API 响应,其持续时间与所示垃圾收集的长度非常相似(在几毫秒内)。

这里有一些典型的例子(这些都是在几秒钟内产生的):

[GC 3373764K->3336654K(10468736K), 0.6677560 secs]
[GC 3472974K->3427592K(10468736K), 0.5059650 secs]
[GC 3563912K->3517273K(10468736K), 0.6844440 secs]
[GC 3622292K->3589011K(10468736K), 0.4528480 secs]

问题是我认为 UseConcMarkSweepGC 会避免这种情况,或者至少让它变得非常罕见。相反,超过 100 毫秒的延迟几乎每分钟发生一次或更多(尽管超过 1 秒的延迟相当罕见,可能每 10 或 15 分钟一次)。

另一件事是,我认为只有 FULL GC 会导致线程暂停,但这些似乎不是 full GC。

需要注意的是,大部分内存都被使用软引用的 LRU 内存缓存占用。

任何帮助或建议将不胜感激。

【问题讨论】:

    标签: java performance garbage-collection jvm jvm-arguments


    【解决方案1】:

    首先,如果您还没有这样做,请查看Java SE 6 HotSpot[tm] Virtual Machine Garbage Collection Tuning 文档。该文档说:

    并发收集器使用 应用程序线程仍在运行,因此只有短暂的停顿被 应用程序线程。但是,如果并发收集器无法完成 在老生代填满之前回收无法访问的对象,或者如果 分配中的可用空闲空间块不能满足 终身代,然后应用暂停,收集完成 所有应用程序线程都停止了。无法完成收藏 同时被称为并发模式故障,并表示需要 调整并发收集器参数。

    稍后会...

    并发收集器在运行期间暂停应用程序两次 并发收集周期。

    我注意到那些 GC 似乎并没有释放太多内存。也许你的许多物品都是长寿的?您可能希望调整生成大小和其他 GC 参数。从许多标准来看,10 Gig 是一个 巨大 堆,我天真地期望 GC 需要更长的时间来处理这么大的堆。尽管如此,1 秒是一个很长的停顿时间,它表明要么有问题(你的程序正在生成大量不需要的对象,要么正在生成难以回收的对象,或者其他什么),要么你只需要调整 GC。

    通常,我会告诉某人,如果他们必须调整 GC,那么他们还有其他问题需要先解决。但是对于这种规模的应用程序,我认为你落入了“需要比普通程序员更了解 GC”的领域。

    正如其他人所说,您需要分析您的应用程序以查看瓶颈所在。您的 PermGen 对于分配给它的空间是否太大?您是否在创建不必要的对象? jconsole 至少可以显示有关 VM 的最少信息。这是一个起点。然而,正如其他人所指出的,您很可能需要比这更高级的工具。

    祝你好运。

    【讨论】:

      【解决方案2】:

      既然您提到了缓存的愿望,我猜您的大部分大堆都被该缓存占用了。您可能希望限制缓存的大小,以确保它永远不会尝试增长到足以填满终身代。不要仅仅依靠SoftReference 来限制大小。当老一代被软引用填充时,旧的引用将被清除并成为垃圾。将创建新的引用(可能是相同的信息),但会很快清除,因为可用空间不足。最终,tenured 空间充满了垃圾,需要清理。

      考虑调整-XX:NewRatio 设置。默认为 1:2,意味着三分之一的堆分配给新生代。对于一个大堆来说,这几乎总是太多了。您可能想尝试 9 之类的东西,这样可以为老年代保留 10 Gb 堆中的 9 Gb。

      【讨论】:

        【解决方案3】:

        事实证明,堆的一部分被换出到磁盘,因此垃圾收集必须将一堆数据从磁盘拉回内存。

        我通过将 Linux 的“swappiness”参数设置为 0 解决了这个问题(这样它就不会将数据交换到磁盘上)。

        【讨论】:

        • 我意识到这是很久以前的事了,但对于未来的访问者来说:在这么大的堆上,考虑为 Java 启用大页面。 Hugepages 也是不可交换的,因此它们将解决交换问题。
        【解决方案4】:

        以下是我发现的一些可能很重要的事情。

        • JSON-RPC 可以生成很多对象。没有 XML-RPC 那么多,但仍然值得关注。在任何情况下,您似乎确实以每秒 100 MB 的速度生成了同样多的对象,这意味着您的 GC 运行的时间比例很高,并且可能会增加您的随机延迟。即使 GC 是并发的,您的硬件/操作系统也很可能在负载下表现出不理想的随机延迟。
        • 查看您的内存库架构。在 Linux 上,命令是 numactl --hardware。如果您的 VM 被拆分到多个内存库中,这将显着增加您的 GC 时间。 (它也会减慢您的应用程序,因为这些访问的效率可能会大大降低)您对内存子系统的工作越努力,操作系统就越有可能不得不移动内存(通常是大量),结果您会出现剧烈的停顿( 100 毫秒并不奇怪)。不要忘记您的操作系统不仅仅是运行您的应用。
        • 考虑压缩/减少缓存的内存消耗。如果您正在使用多 GB 的缓存,则值得研究比现有方法进一步减少内存消耗的方法。
        • 我建议您同时使用内存分配跟踪和 CPU 采样来分析您的应用程序。这可能会产生非常不同的结果,并且通常会指出此类问题的原因。

        使用这些方法,可以将 RPC 调用的延迟降低到200 微秒以下,并将 GC 时间降低到 1-3 毫秒,从而影响不到 1/300 的调用。 p>

        【讨论】:

          【解决方案5】:

          我还建议GCViewer 和一个分析器。

          【讨论】:

            【解决方案6】:

            一些开始寻找的地方:

            我还会通过分析器运行代码。我喜欢 NetBeans 中的那个,但也有其他的。您可以实时查看 gc 行为。 Visual VM 也能做到这一点……但我还没有运行它(一直在寻找理由……但还没有时间或需要)。

            【讨论】:

              【解决方案7】:

              我个人没有使用过这么大的堆,但我在使用 Oracle/Sun Java 1.6.x 的以下开关时体验到了一般非常低的延迟:

              -Xincgc -XX:+UseConcMarkSweepGC -XX:CMSIncrementalSafetyFactor=50
              -XX:+UseParNewGC
              -XX:+CMSConcurrentMTEnabled -XX:ConcGCThreads=2 -XX:ParallelGCThreads=2
              -XX:CMSIncrementalDutyCycleMin=0 -XX:CMSIncrementalDutyCycle=5
              -XX:GCTimeRatio=90 -XX:MaxGCPauseMillis=20 -XX:GCPauseIntervalMillis=1000
              

              在我看来,重要的部分是在老年代使用 CMS,在年轻一代使用 ParNewGC。此外,这为 CMS 增加了一个相当大的安全系数(默认为 10% 而不是 50%)并要求较短的暂停时间。由于您的目标是 25 毫秒响应时间,我会尝试将 -XX:MaxGCPauseMillis 设置为更小的值。您甚至可以尝试为并发 GC 使用两个以上的内核,但我这不值得 CPU 使用。

              您可能还应该检查HotSpot JVM GC cheat sheet

              【讨论】:

                猜你喜欢
                • 2014-11-02
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2020-03-16
                • 1970-01-01
                • 1970-01-01
                • 2014-03-18
                • 1970-01-01
                相关资源
                最近更新 更多