鼓励 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 的堆大小不断增加(但 usedMemory() 不断减少)