为什么JVM不使用更多的堆内存

3
我尝试通过以下方式增加堆内存:-Xmx9g -Xms8g。说实话,只是因为我可以这样做。现在我想知道,为什么JVM不使用更多的内存,并减少垃圾回收的频率。
系统信息:
JVM: Java HotSpot(TM) 64-Bit Server VM (24.51-b03, 混合模式)
Java版本:1.7.0_51,供应商Oracle Corporation 编辑:
我想改进我的配置以进行建模过程(吞吐量优于响应性)。

1
当每个应用程序都有“因为我可以,所以我占用大量堆空间”的心态时,会发生什么?剩下的堆空间不多了。 - ChiefTwoPencils
你的虚拟机上还有其他GC设置吗?你想在GC之前使用完整个堆的原因是什么?应用程序的许多细节会影响GC,例如大量短寿命对象,可以经常进行收集而无需完全暂停。为什么要等到浴缸溢出才关掉水龙头呢? - James
请提供您可能拥有的任何GC特定设置。并且您是否看到GC以这种方式运行会产生任何显着的性能影响? - Praba
2个回答

4
Java 1.7中的HotSpot JVM将堆分为几个空间,与本文讨论相关的是:
- Eden:新对象所在的位置 - Survivor:如果需要在Eden被GC后使用,则对象会进入Survivor 当您分配一个新对象时,它会简单地附加到Eden空间。一旦Eden已满,就会进行所谓的“小型收集”。仍然可达的Eden中的对象会被复制到Survivor,然后清除Eden(因此收集任何未复制的对象)。 您想要填满Eden,而不是整个堆。 例如,看看这个简单的应用程序:
public class Heaps {
  public static void main(String[] args) {
    Object probe = new Object();
    for (;;) {
      Object o = new Object();
      if (o.hashCode() == probe.hashCode()) {
        System.out.print(".");
      }
    }
  }
}
probe 的作用只是为了确保 JVM 不能优化掉循环。重复的 new Object() 才是我们真正需要的。如果使用默认的 JVM 选项运行此代码,您将得到一个与您看到的类似的图形。对象被分配在 Eden 区域,这只是整个堆的一小部分。一旦 Eden 区填满,它就会触发一次轻量级垃圾收集,清除所有新对象,并将堆使用量降至接近 0 的“基线”水平。
那么,如何填满整个堆?将 Eden 区域设置得非常大!Oracle 发布了其堆调优参数,这里相关的两个参数是 -XX:NewSize-XX:MaxNewSize。当我使用 -Xms9g -XX:NewSize=8g -XX:MaxNewSize=8g 运行上述程序时,我得到了更接近您预期的结果。

enter image description here

在一次运行中,这几乎用完了所有堆空间和我指定的Eden空间;随后的运行只占据了我指定的Eden空间的一小部分,如您在此处所见。我不太确定原因是什么。
VisualVM有一个名为Visual GC的插件,可以让您查看有关堆的更多详细信息。这是我从我的屏幕截图中拍摄的,恰好显示了Eden几乎满了,而老年空间几乎为空(因为循环中的那些新对象new Object()没有在Eden收集中存活下来)。

enter image description here


谢谢您的描述…我知道Spaces,但我仍然不明白:在某些情况下,减少GC频率不是更高效吗? - Franz Ebner
在某些情况下,可以。这就是为什么设计JVM的人提供并记录了堆调整参数。默认值被选择为适用于广泛的一般应用程序,如果您需要进行精细调整(例如以更多的RAM为代价来减少GC频率),则可以进行调整。 - yshavit
那要看情况;如果你降低频率,这意味着你会得到更少但更加CPU密集的收集,因此更有可能干扰应用程序响应时间。我见过垃圾回收器每天只允许运行一次的情况,而这可能需要一个完整的小时才能完成。 - Gimby
@biziclop @yshavit @gimby 感谢您们的时间! - Franz Ebner
@yshavit,我可能在答案(或问题)中漏掉了某些内容,但为什么“你想要的是填满Eden,而不是整个堆。”? - matt freake
1
@Disco3 因为无论整个堆是如何的情况,当伊甸园区满了时,次要GC都会发生。如果伊甸园区占用了你100mb堆的5%(默认情况下更多),并且所有对象的生命周期都很短暂(就像我的例子一样),那么在文件达到5mb后,你将会遇到GC,而其他95mb的堆将永远不会被使用——这正是OP问题所涉及的情况。 - yshavit

3

(我将从不同角度尝试回答“为什么”的问题。)

通常,您希望通过GC设置平衡两个方面:吞吐量和响应性。

吞吐量由整体进行GC所花费的时间确定,响应性则由单个GC运行的长度确定。默认的GC设置旨在为您提供两者之间的合理折衷。

高吞吐量意味着在长时间内测量,GC开销将会更少。另一方面,高响应性将使得短代码片段在相同时间内运行的可能性更大,并且不会被GC阻塞很长时间。

如果您调整GC参数以允许填充所有9GB的堆,则会发现吞吐量可能已经增加(虽然我不确定它总是会增加),但当GC最终运行时,您的应用程序会冻结数秒钟。这对于运行单个长时间计算的进程可能是可以接受的,但对于HTTP服务器甚至桌面应用程序来说都不是这样。

故事的寓意是:您可以调整GC以执行任何您想要的操作,但除非您诊断出了特定的问题(正确地),否则您很可能比使用默认设置更糟糕。

更新: 既然您似乎想要高吞吐量,但不介意暂停,那么您最好的选择是使用吞吐量收集器(-XX:+UseParallelGC)。我显然无法给出确切的参数,您必须使用this guide观察每个更改的影响来调整它们。我可能不需要告诉您这一点,但我的建议是始终只更改一个参数,然后检查其对性能的影响。

@FranzEbner 我明白了,所以问题更多是关于如何去做。 - biziclop
我认为我看到的是,处理器因快速GC而负载很重。但考虑到我的RAM,增加使用量不会成为问题。 - Franz Ebner
看看我的更新,希望能解决你的问题。 - biziclop

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