鼓励JVM进行垃圾回收而不是增加堆大小?

21
注意,当我说“JVM”时,实际上是指“Hotspot”,我正在运行最新的Java 1.6更新版。
举个例子:
我的JVM使用-Xmx设置为1gb。目前,堆分配了500mb,其中有450mb被使用。程序需要在堆上再加载200 mb数据。当前,堆中有300mb的“可回收”垃圾(我们假设它全部在最年长的一代中)。
在正常操作下,当JVM达到约700 mb或者适当时会进行垃圾回收。
在这种情况下,我希望JVM首先进行垃圾回收,然后再分配新的空间,以便我们的堆大小保持在500mb,并且已使用的堆为350mb。
是否有JVM参数组合可以实现这一点?
4个回答

15

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

  • -XX:MinHeapFreeRatio - 当代的可用空间百分比低于此值时,该代将被扩展以达到此百分比。默认值为40。
  • -XX:MaxHeapFreeRatio - 当代的可用空间百分比超过此值时,该代将收缩以达到此值。默认值为70。

您还可以通过指定-XX:+UseConcMarkSweepGC来尝试并发GC。根据您的应用程序,这可能会在增加CPU周期的成本下使堆大小更低。

当JVM优化时,它将使用您指定的可用内存。您可以指定较低的内存量,例如-Xmx768m,使其保持在控制范围内,这可能会正常运行,但在负载较重的情况下会增加内存耗尽的风险。实际上,减少整体内存使用的唯一方法是编写使用更少内存的代码 :)


1
-1 这些选项仅适用于串行垃圾收集器(即不再使用的垃圾收集器)。此外,CMS通常比G1差,尽管您的答案可能早于它。 - Aleksandr Dubinsky
@AleksandrDubinsky 尽管 G1 是非常好的垃圾回收器,但在 Java 7 上它对于任何需要频繁进行类加载和卸载的低延迟系统来说都不太适用,因为它无法清理永久代(permgen)。而 Java 8 则不存在这个问题。 - Leliel
@Leliel,我认为在Java 7中没有任何收集器收集PermGen。这就是为什么它被称为“永久代”的原因。 - Aleksandr Dubinsky
@AleksandrDubinsky CMS收集器可以配置以收集PermGen。-XX:+CMSClassUnloadingEnabled - Leliel

14

更新 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>。将最大暂停时间设置得更低通常会使收集器缩小堆大小。但是,如果暂停时间已经很短,堆不会变小。另一方面,如果最大暂停时间设置得太低,应用程序可能会花费所有时间来进行收集。将较低的gc时间比率设置通常也会使堆变小。您告诉gc愿意在返回较小的堆的同时投入更多CPU时间进行收集。但是,这只是一个提示,可能没有任何效果。在我看来,针对最小化堆大小的目的,并行收集器几乎无法调整。如果您让它运行一段时间且应用程序的行为保持不变,则此收集器效果更好(它会自动调整)。
  • CMS收集器(-XX:+UseConcMarkSweepGC)通常需要更大的堆才能实现其主要目标——低暂停时间,因此我不会讨论它。
  • 新的G1收集器(-XX:+UseG1GC)不像旧收集器那样愚蠢。我发现它会自行选择较小的堆大小。它有很多调整选项,尽管我只是开始研究它们。-XX:InitiatingHeapOccupancyPercent-XX:G1HeapWastePercent-XX:G1ReservePercent-XX:G1MaxNewSizePercent可能会引起兴趣。

请查看java标志列表


2
通常情况下,在分配额外的 200MB 对象之前,您可以调用 System.gc() 方法,但这只是向 JVM 提示它可以或不可以执行垃圾回收,并且无论如何,您都不知道何时进行垃圾回收。正如文档中所述,这只是一个尽力而为的请求。
顺便提一句,垃圾回收是一个繁重的操作,因此如果没有特别需要,就不要试图强制执行它。
需要了解的是:如果您设置了 -Xmx1G,那么您告诉 JVM 堆的最大空间为 1GB,既然您已经指定了这个值,我不明白为什么它会试图保持较低的值,如果它知道 1GB 仍然可行(我们处于内存管理语言的上下文中)。如果您不希望堆增加那么多,请降低最大值,这样在分配新对象之前将被强制执行 GC,否则为什么要告诉它使用 1GB?

1
在我看来,"现代"编程语言中不需要释放未使用内存的概念是最大的问题。我分享Electrons_Ahoy的担忧。不幸的是,计算机不再像以前那样变得更快了。我们必须编写更好的软件! - Sebastian Dusza
如果你想忘记内存分配,那就要做出这种权衡,这并不奇怪,也不会禁止你使用C或C++手动管理任何东西。但是,如果你选择一个能够自我最佳行为的环境,那么你就不应该试图强制其行为。你的论点大多无用,因为我们处于Java的上下文中,没有人强迫你使用它 :) 这就像与开着一辆污染严重的大车的人争论一样,只需骑上自行车,忘记污染即可。 - Jack
1
我个人认为,GC(垃圾回收)的概念应该进行增强/修改。Java/.NET和其他编程语言是在计算机速度不断提升的时代诞生的。然而现在的情况已经完全不同了:)。我喜欢Java,希望能有类似于delete操作符的东西出现:) - Sebastian Dusza
@Seba,这实际上是因为Java针对服务器而设计的,服务器上只运行一个应用程序,它应该从操作系统中获取所有内存并根据自己的需要使用它。显然,在客户端情况下完全不同。唉。 - Aleksandr Dubinsky
1
@SebastianDusza 删除操作符绝不会快速。它需要底层实现引入一个全新的数据结构,以跟踪已删除的内容。当然,如果删除操作符只更新“已使用内存”计数器,以让人们满意,同时将实际工作留给GC,只有在真正需要时才运行,这可能会帮助一些人。 - Holger
显示剩余2条评论

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接