关于java:积极的垃圾收集器策略

Agressive garbage collector strategy

我正在运行一个创建和忘记大量对象的应用程序,长现有对象的数量确实增长缓慢,但与短期对象相比,这是非常少的。这是一个具有高可用性要求的桌面应用程序,需要每天24小时打开。大部分工作都是在一个线程上完成的,这个线程只会使用它可以获得它的所有CPU。

在过去,我们在重载下看到了以下情况:
使用的堆空间缓慢上升,因为垃圾收集器收集的内存少于新分配的内存量,使用的堆大小缓慢增长并最终接近指定的最大堆。此时,垃圾收集器将大量启动,并开始使用大量资源来防止超过最大堆大小。这样可以减慢应用程序的速度(缓慢10倍),此时GC大部分时间会在几分钟后成功清理垃圾或者失败并抛出OutOfMemoryException,这两种情况都不可接受。

使用的硬件是四核处理器,至少4GB内存运行64位Linux,如果需要,我们可以使用所有这些。目前,该应用程序大量使用单个核心,该核心大部分时间都在运行单个核心/线程。其他核心大多是空闲的,可用于垃圾收集。

我有一种感觉,垃圾收集器应该在早期阶段更积极地收集,远在它耗尽内存之前。我们的应用程序没有任何吞吐量问题,低暂停时间要求比吞吐量更重要,但远不如不接近最大堆大小重要。如果单个繁忙线程仅以当前速度的75%运行是可以接受的,只要这意味着垃圾收集器可以跟上创建的步伐。简而言之,性能的稳定下降优于我们现在看到的突然下降。

我已经仔细阅读了Java SE 6 HotSpot [tm]虚拟机垃圾收集调整,这意味着我很好地理解了这些选项,但是我仍然觉得很难选择正确的设置,因为我的要求与文中讨论的内容有点不同。

目前我正在使用带有选项-XX:GCTimeRatio=4的ParallelGC。这比时间比的默认设置好一点,但我感觉GC允许运行的设置比它更多。

为了监控,我主要使用jconsole和jvisualvm。

我想知道您为上述情况推荐的垃圾收集选项。另外,我可以看看哪个GC调试输出更好地理解瓶颈。

编辑:
我理解这里有一个非常好的选择就是创造更少的垃圾,这是我们真正考虑的事情,但是我想知道我们如何通过GC调整来解决这个问题,因为这是我们可以更容易做到的事情并推出更多比改变大量的源代码快。此外,我运行了不同的内存分析器,我了解垃圾的用途,我知道它包含可以收集的对象。

我在用:

1
2
3
java version"1.6.0_27-ea"
Java(TM) SE Runtime Environment (build 1.6.0_27-ea-b03)
Java HotSpot(TM) 64-Bit Server VM (build 20.2-b03, mixed mode)

使用JVM参数:

1
-Xmx1024M and -XX:GCTimeRatio=4

编辑回复Matts评论:
大多数内存(和cpu)用于构建表示当前情况的对象。当情况迅速变化时,其中一些将被立即丢弃,如果没有更新进入一段时间,其他一些将具有中等寿命。


您没有提到正在运行的JVM的哪个版本,这是至关重要的信息。你也没有提到应用程序运行的时间长短(例如,它是一个工作日的长度?一周?更少?)

其他几点

  • 如果你不断地将物品泄漏到终身,因为你的分配速度比你的年轻人可以被扫描的速度快,那么你的世代就不合适了。您需要对应用程序的行为进行一些正确的分析才能正确调整它们的大小,您可以使用visualgc进行此操作。
  • 吞吐量收集器设计为接受单个大停顿,而不是许多较小的暂停,其好处是它是一个紧凑的收集器,它可以实现更高的总吞吐量
  • CMS的存在是为了服务于频谱的另一端,即更多更小的暂停但总吞吐量更低。缺点是它没有压缩,所以碎片可能是一个问题。碎片问题在6u26改进了,所以如果你不在那个版本上,那么可能是升级时间。请注意,您所注意到的"流失到终身"效果会加剧碎片问题,并且,如果时间过长,这将导致促销失败(也就是计划外的完整gc和同事STW暂停)。我之前在这个问题上写过这个问题的答案

  • 如果你正在运行一个带有> 4GB RAM和最近JVM的64位JVM,请确保你-XX:+UseCompressedOops否则你只是浪费空间,因为64位JVM占用相同工作负载的32位JVM空间的1.5倍它(如果你不是,升级以获得更多RAM)
  • 您可能还想阅读我在这个主题上写的另一个答案,这个答案将用于评估您的幸存者空间和范围。伊甸园恰当。基本上你想要实现的是;

    • 伊甸园足够大,不经常收集
    • 幸存者空间的大小与期限阈值相匹配
    • 设定一个终点阈值,以尽可能确保只有真正长寿的物体才能使其成为终身

    因此,假设您有一个6G堆,您可能会执行类似5G eden + 16M幸存者空间+ 1的持久阈值。

    基本过程是

  • 分配到伊甸园
  • 伊甸园填满了
  • 活物被扫入幸存者的空间
  • 来自幸存者空间的活体对象要么被复制到空间要么被提升到终点(取决于时效阈值和可用空间,并且没有时间它们被从1复制到另一个)
  • 在伊甸园留下的任何东西都被扫除了
  • 因此,给定适合您的应用程序的分配配置文件的空间,完全可以配置系统,以便它很好地处理负载。对此有一些警告;

  • 您需要一些长时间运行的测试来正确执行此操作(例如,可能需要数天才能解决CMS碎片问题)
  • 你需要做几次测试才能取得好成绩
  • 你需要在GC配置中一次更改一件事
  • 您需要能够向应用程序提供合理可重复的工作负载,否则很难客观地比较不同测试运行的结果
  • 如果工作量不可预测并且具有巨大的峰值/谷值,那么这将非常难以可靠地完成
  • 积分1-3表示这可能需要很长时间才能正确。另一方面,你可以快速地使它变得足够好,这取决于你是多么肛门!

    最后,回应Peter Lawrey的观点,如果你对对象分配非常严格,你可以节省很多麻烦(尽管引入了一些其他的麻烦)。


    已经引入稳定Java 1.7G1GC算法运行良好。您只需指定要在应用程序中使用的最长暂停时间。 JVM将为您处理所有其他事情。

    关键参数:

    1
    -XX:+UseG1GC -XX:MaxGCPauseMillis=1000

    还有一些参数需要配置。如果使用4 GB RAM,请将区域大小配置为4 GB / 2048块,大约为2 MB

    1
    -XX:G1HeapRegionSize=2

    如果你有8个核心CPU,则微调两个以上的参数

    1
    -XX:ParallelGCThreads=4 -XX:ConcGCThreads=2

    除了这些参数,请将其他参数值保留为默认值

    -XX:TargetSurvivorRatio

    有关G1GC的更多详细信息,请查看oracle网站。

    1
    -XX:G1HeapRegionSize=n

    设置G1区域的大小。该值为2的幂,范围从1MB到32MB。目标是根据最小Java堆大小拥有大约2048个区域。

    1
     -XX:MaxGCPauseMillis=200

    设置所需最大暂停时间的目标值。默认值为200毫秒。指定的值不适合您的堆大小。

    1
    -XX:ParallelGCThreads=n

    设置STW工作线程的值。将n的值设置为逻辑处理器的数量。 n的值与逻辑处理器的数量相同,最大值为8。

    如果有超过八个逻辑处理器,则将n的值设置为逻辑处理器的大约5/8。这在大多数情况下都有效,除了较大的SPARC系统,其中n的值可以是逻辑处理器的大约5/16。

    1
    -XX:ConcGCThreads=n

    来自oracle的建议:

    在评估和微调G1 GC时,请记住以下建议:

  • 年轻代大小:避免使用-Xmn选项或任何或其他相关选项(如-XX:NewRatio)明确设置年轻代大小。 <5233>。

  • 暂停时间目标:评估或调整任何垃圾收集时,始终存在延迟与吞吐量的权衡。 G1 GC是一个增量垃圾收集器,具有统一的暂停,但在应用程序线程上也有更多的开销。 The throughput goal for the G1 GC is 90 percent application time and 10 percent garbage collection time

  • 最近我用4G堆的G1GC算法取代了CMS,几乎相同的年轻和分组。老一代。我设置了MaxGCPause时间,结果很棒。


    我尝试的第一个VM选项是增加NewSizeMaxNewSize并使用其中一个并行GC算法(尝试UseConcMarkSweepGC,它旨在"保持垃圾收集暂停")。

    要确认您所看到的暂停是由GC引起的,请打开详细GC记录(-verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps)。有关如何阅读这些日志的更多信息,请访问在线。

    要了解瓶颈,请在分析器中运行该应用程序。拍摄快照。然后,让应用程序做一会儿。拍摄另一个堆快照。为了查看占用所有空间的内容,请在第二个堆快照之后查找更多内容。 Visual VM可以做到这一点,但也考虑MAT。

    或者,考虑使用-XX:+HeapDumpOnOutOfMemoryError以便获得真实问题的快照,而不必在其他环境中重现它。可以使用相同的工具分析保存的堆 - MAT等。

    但是,您可能会得到一个OutOfMemoryException,因为您有内存泄漏或者因为您运行的最大堆大小太小。详细的GC日志记录应该可以帮助您回答这两个问题。


    您可以尝试减小新尺寸。这将使它成为更多,更小的集合。但是,它可以使这些短暂的物体进入终身空间。另一方面,您可以尝试增加NewSize,这意味着更少的对象将从年轻一代传出。

    然而,我的偏好是创建更少的垃圾,GC将以更一致的方式运行。不要自由创建对象,而是尝试重新使用它们或回收对象。你必须要小心,这不会造成比它的价值更多的麻烦,但你可以减少在某些应用程序中显着创建的垃圾量。我建议使用内存分析器,例如YourKit帮助您识别最大的击球手。

    一个极端的例子是创造如此少的垃圾,它不会整天收集(即使是次要收藏)。它可能是服务器端应用程序(但可能不适用于GUI应用程序)