鼓励 JVM 进行 GC 而不是增加堆?

Posted

技术标签:

【中文标题】鼓励 JVM 进行 GC 而不是增加堆?【英文标题】:Encourage the JVM to GC rather than grow the heap? 【发布时间】:2011-07-18 09:01:06 【问题描述】:

(请注意,当我说“JVM”时,我的意思是“热点”,我正在运行最新的 Java 1.6 更新。)

示例情况:

我的 JVM 运行时 -Xmx 设置为 1gb。目前,堆分配了 500mb,其中使用了 450mb。该程序需要在堆上再加载 200 mb。目前,堆中有 300mb 的“可收集”垃圾(我们假设它们都在最老的一代中。)

在正常操作下,JVM 会将堆增加到 700 mb 左右,并在到达时进行垃圾收集。

在这种情况下,我希望 JVM 先 gc,然后分配新的东西,这样我们最终的堆大小保持在 500mb,使用的堆大小保持在 350mb。

是否有一个 JVM 参数组合可以做到这一点?

【问题讨论】:

【参考方案1】:

您可以尝试指定-XX:MinHeapFreeRatio-XX:MaxHeapFreeRatio 来控制堆扩展和收缩:

-XX:MinHeapFreeRatio - 当一代中可用空间的百分比低于此值时,一代将扩展以满足此百分比。默认值为 40。 -XX:MaxHeapFreeRatio - 当一代中可用空间的百分比超过此值时,一代将收缩以满足此值。默认值为 70。

您可能还想通过指定 -XX:+UseConcMarkSweepGC 来试验并发 GC。根据您的应用程序,它可以以额外的 CPU 周期为代价来降低堆大小。

JVM 会在最佳情况下使用您指定的可用内存。您可以指定较低的数量,例如 -Xmx768m 以保持它包含在内并且它可能运行良好,尽管您会增加在重负载情况下耗尽内存的风险。真正使用更少内存的唯一方法是编写使用更少内存的代码:)

【讨论】:

-1 这些选项仅适用于 serial 垃圾收集器(即,没有人再使用的垃圾收集器)。此外,CMS 通常不如G1,尽管您的回答可能早于它。 @AleksandrDubinsky 虽然G1 是一个非常好的收集器,但在Java 7 上,它完全不适合任何需要大量加载和卸载类的延迟敏感系统,因为它不收集permgen。 Java 8 没有这个问题 @Leliel 我不认为任何收集器在 Java 7 上收集 permgen。因此名称为“永久代”。 @AleksandrDubinsky CMS 收集器可以配置为收集 permgen。 -XX:+CMSClassUnloadingEnabled【参考方案2】:

更新 Java 15 增加了两个收集器。 ZGC 中的一些相关选项是:

-XX:+UseZGC启用ZGC -XX:+ZProactive-XX:+ZUncommit 默认启用。 ZGC 将主动尝试释放垃圾对象并将释放的内存释放给操作系统。其余选项会调整完成的积极程度。 -XX:ZCollectionInterval=seconds GC 运行的频率。设置为几秒钟以确保尽快清理垃圾。 (默认为 0,即不保证 GC 会运行。) -XX:ZUncommitDelay=seconds 设置堆内存在未提交之前必须未使用的时间量(以秒为单位)。默认情况下,此选项设置为 300(5 分钟)。设置较低的值可以更快地取回内存 -XX:SoftMaxHeapSize 只要应用程序使用的内存超过此限制,GC 就会更频繁地触发。 (这可能是 OP 正在寻找的答案。) -XX:SoftRefLRUPolicyMSPerMB 不确定此选项的相关频率,但它控制一些缓存对象在内存中的保留时间。

原答案HotSpot 中有四种(实际上是五种)不同的垃圾收集器,每个选项都有不同的选择。

串行收集器有-XX:MinHeapFreeRatio-XX:MaxHeapFreeRatio,让您或多或少直接控制行为。但是,我对串行收集器没有太多经验。 并行收集器 (-XX:+UseParallelOldGC) 有 -XX:MaxGCPauseMillis=<millis>-XX:GCTimeRatio=<N>。设置较低的最大暂停时间通常会导致收集器使堆变小。但是,如果暂停时间已经很短,堆就不会变小。 OTOH,如果最大暂停时间设置得太低,应用程序可能会将所有时间都花在收集上。设置较低的 gc 时间比率通常也会使堆更小。您告诉 gc 您愿意将更多的 CPU 时间用于收集以换取更小的堆。但是,这只是一个提示,可能没有任何效果。在我看来,为了最小化堆的大小,并行收集器几乎是不可调整的。如果您让它运行一段时间并且应用程序的行为保持不变,则此收集器会更好地工作(它会自行调整)。 CMS 收集器 (-XX:+UseConcMarkSweepGC) 通常需要更大的堆来实现其低暂停时间的主要目标,因此我不会讨论它。 新的 G1 收集器 (-XX:+UseG1GC) 不像旧的收集器那样脑残。我发现它自己选择了较小的堆大小。它有很多调整选项,虽然我才刚刚开始研究它们。 -XX:InitiatingHeapOccupancyPercent-XX:G1HeapWastePercent-XX:G1ReservePercent-XX:G1MaxNewSizePercent 可能感兴趣。

看看list of java flags。

【讨论】:

【参考方案3】:

您通常可以在分配额外的 200MB 对象之前调用 System.gc() 方法,但这只是对 JVM 的一个提示,它可以或不能遵循,并且无论如何您都不知道何时发生这种情况.. 如文档中所述,这是一个尽力而为的请求。

顺便说一句,垃圾收集是一项繁重的操作,所以如果没有特别需要这样做,就不要试图强迫它。

要知道:如果你设置-Xmx1G,那么你告诉JVM 1GB 是堆的最大空间,既然你指定了,我不明白为什么它应该尽量保持低如果它知道 1GB 仍然可以(我们处于内存管理语言的上下文中)。如果你不希望堆增加那么多,只需减小最大值,以便在分配新对象之前强制执行 GC,否则你为什么告诉它使用 1GB?

【讨论】:

在我看来,无需释放未使用内存的概念是当今“现代”编程语言的主要问题。我分享 Electrons_Ahoy 的担忧。不幸的是,计算机不再像以前那样变得更快了。我们必须编写更好的软件! 如果你想忘记内存分配,这就是权衡,没有什么奇怪的,也没有什么禁止你使用 C 或 C++ 并手动管理你想要的任何东西。但是,如果您选择的环境能够以最佳方式自行处理,那么您不应该试图强迫它的行为。你的论点大多是无用的,因为我们是在 Java 的上下文中,没有人强迫你使用它:) 这就像与一辆污染严重的汽车的人争论,只要骑自行车,忘记污染.. 这可能很有用,因为当使用的堆较小时,完整的 gc 会更便宜。如果您可以在“fullGC now->recover 500M->add 500M of new objects”和“add 500M->trigger an automatic GC->recover 500M”之间进行选择,第一个会更便宜。此外,尽管文档确实明确指出 System.gc() 将被视为“建议”,但它确实有效,就好像它在我有限的混乱中被写成强制性一样。并不是说这一定是个好主意 - 对于 1G 堆,fullgc 会导致明显的挂起 - 至少会完全锁定几秒钟。 是的,System.gc() 大部分时间都可以工作,但它不能保证它.. 所以使用它但要注意事实!就是这样,我可以确认,在我有限的使用中,它也总是能达到我的预期。 我只是认为应该扩充/修改 GC 概念。 Java/.NET 和其他在计算机越来越快的世界中创建的。这不再是真的,现实完全不同:)。我喜欢 Java,希望有类似删除操作符的东西 :)【参考方案4】:

我建议在这里看看:

http://www.petefreitag.com/articles/gctuning/

【讨论】:

以上是关于鼓励 JVM 进行 GC 而不是增加堆?的主要内容,如果未能解决你的问题,请参考以下文章

关于增加 JVM 的堆大小

jvm - 常用调优启动参数配置

jvm的那些设置参数你都知道吗

JVM 的堆大小不断增加(但 usedMemory() 不断减少)

具有大量引用字段(数组除外)的对象是否会破坏Hotspot JVM的GC堆遍历性能?

如何增加 Groovy 的 JVM 堆大小?