积极的垃圾收集策略

Thi*_*ler 33 java garbage-collection heap-memory

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

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

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

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

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

目前我正在使用带有选项的ParallelGC -XX:GCTimeRatio=4.这比时间比的默认设置好一点,但我感觉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)
Run Code Online (Sandbox Code Playgroud)

使用JVM参数:

-Xmx1024M and -XX:GCTimeRatio=4 
Run Code Online (Sandbox Code Playgroud)

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

Mat*_*att 21

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

其他几点

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

您可能还想阅读我在这个主题写的另一个答案,它会适当地调整您的幸存者空间和伊甸园的大小.基本上你想要实现的是;

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

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

基本过程是

  1. 分配到伊甸园
  2. 伊甸园填满了
  3. 活物被扫入幸存者的空间
  4. 来自幸存者空间的活体对象要么被复制到空间要么被提升到终身空间(取决于可用的持久阈值和空间,并且没有将它们从1复制到另一个)
  5. 在伊甸园留下的任何东西都被扫除了

因此,给定适合您的应用程序的分配配置文件的空间,完全可以配置系统,以便它很好地处理负载.对此有一些警告;

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

积分1-3表示这可能需要很长时间才能正确.另一方面,你可以快速地使它足够好,这取决于你是多么肛门!

最后,呼应彼得Lawrey的时候,你可以节省很多的麻烦(虽然引入一些其他的麻烦)如果你真的严格有关对象分配.


Rav*_*abu 6

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

关键参数:

-XX:+UseG1GC -XX:MaxGCPauseMillis=1000 
Run Code Online (Sandbox Code Playgroud)

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

-XX:G1HeapRegionSize=2  
Run Code Online (Sandbox Code Playgroud)

如果你有8核CPU,再微调两个参数

-XX:ParallelGCThreads=4 -XX:ConcGCThreads=2 
Run Code Online (Sandbox Code Playgroud)

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

-XX:TargetSurvivorRatioETC。

请访问oracle 网站了解有关 的更多详细信息G1GC

-XX:G1HeapRegionSize=n
Run Code Online (Sandbox Code Playgroud)

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

 -XX:MaxGCPauseMillis=200
Run Code Online (Sandbox Code Playgroud)

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

-XX:ParallelGCThreads=n
Run Code Online (Sandbox Code Playgroud)

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

如果逻辑处理器数量超过 8 个,则将 n 的值设置为逻辑处理器的大约 5/8。这在大多数情况下都适用,但较大的 SPARC 系统除外,其中 n 的值约为逻辑处理器的 5/16。

-XX:ConcGCThreads=n
Run Code Online (Sandbox Code Playgroud)

建议甲骨文的

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

  1. 年轻代大小-Xmn:避免使用选项或任何或其他相关选项(例如 )显式设置年轻代大小-XX:NewRatioFixing 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

最近,我用 G1GC 算法替换了 4 GB 堆的 CMS,年轻代和老代的划分几乎相等。我设定了MaxGCPause时间,结果非常棒。