积极的垃圾收集策略

时间:2011-11-02 12:18:56

标签: java garbage-collection heap-memory

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

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

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

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

我已经彻底阅读了Java SE 6 HotSpot[tm] Virtual Machine Garbage Collection Tuning,这意味着我很了解这些选项,但是我仍然觉得很难选择正确的设置,因为我的要求与论文中讨论的内容略有不同。

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

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

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

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

我正在使用:

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参数:

-Xmx1024M and -XX:GCTimeRatio=4 

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

4 个答案:

答案 0 :(得分:20)

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

其他几点

  1. 如果你不断地将物品泄漏到终身,因为你的分配速度比你的年轻人可以被扫描的速度快,那么你的世代的大小就会不正确。您需要对应用程序的行为进行适当的分析才能正确调整它们的大小,您可以使用visualgc进行此操作。
  2. 吞吐量收集器旨在接受单个大型暂停,而不是许多较小的暂停,其好处是它是一个紧凑的收集器,它可以实现更高的总吞吐量
  3. CMS的存在是为了服务于频谱的另一端,即更多更小的暂停但总吞吐量更低。缺点是它没有压缩,所以碎片可能是一个问题。碎片问题在6u26改进了,所以如果你不在那个版本上,那么可能是升级时间。请注意,您所注意到的“流失到终身”效果会加剧碎片问题,并且,如果时间过长,这将导致促销失败(也就是计划外的完整gc和同事STW暂停)。我之前在this question上写了一个关于此的答案
    1. 如果你正在运行一个带有> 4GB RAM和最近JVM的64位JVM,请确保-XX:+UseCompressedOops否则你只是浪费空间,因为64位JVM占用的空间大约是32位的1.5倍没有它的JVM用于相同的工作负载(如果不是,则升级以访问更多RAM)
  4. 您可能还想阅读another answer I've written on this subject,其中包括调整您的幸存者空间&伊甸园恰当。基本上你想要实现的是;

    • 伊甸园足够大,不经常收集
    • 幸存者空间大小与期限阈值匹配
    • 一个终身门槛,旨在尽可能确保只有真正长寿的物品才能成为终身

    因此,假设你有一个6G的堆,你可能会做一些像5G eden + 16M幸存者空间+ 1的终身临界值。

    基本流程是

    1. 分配到伊甸园
    2. 伊甸园填满
    3. 活体对象席卷幸存者空间
    4. 来自幸存者空间的活体物品要么被复制到太空,要么被提升为终身(取决于时效阈值和可用空间以及没有时间从1复制到另一个)
    5. 伊甸园的任何东西都被扫除了
    6. 因此,给定适合您的应用程序分配配置文件的大小的空间,完全可以配置系统,以便它很好地处理负载。对此有一些警告;

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

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

答案 1 :(得分:3)

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

关键参数:

-XX:+UseG1GC -XX:MaxGCPauseMillis=1000 

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

-XX:G1HeapRegionSize=2  

如果您有8个核心CPU,请再调整两个参数

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

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

-XX:TargetSurvivorRatio等。

有关G1GC的详细信息,请查看oracle website

-XX:G1HeapRegionSize=n

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

 -XX:MaxGCPauseMillis=200

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

-XX:ParallelGCThreads=n

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

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

-XX:ConcGCThreads=n
来自oracle的

建议

评估和调整G1 GC时,请牢记以下建议:

  1. 年轻代大小:避免使用-Xmn选项或任何或其他相关选项(例如-XX:NewRatio)明确设置年轻代的大小。 Fixing the size of the young generation overrides the target pause-time goal

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

  3. 最近我用4G堆的G1GC算法取代了CMS,几乎相等的年轻基因和老一代。我设置了MaxGCPause时间,结果很棒。

答案 2 :(得分:2)

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

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

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

答案 3 :(得分:2)

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

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

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

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

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