为什么Java堆的最大大小是固定的?

31

在Java虚拟机启动后增加堆的最大大小是不可能的。这是出于什么技术原因呢?垃圾回收算法是否依赖于固定数量的内存来工作?还是为了安全原因,防止Java应用程序通过消耗所有可用内存来DOS其他系统上的应用程序?


类似的问题:“为什么(Sun)JVM有一个固定的内存使用上限(-Xmx)?” - https://dev59.com/JnA75IYBdhLWcg3wUHWQ - sleske
5个回答

23
在Sun的JVM中,据我所知,整个堆必须分配在连续的地址空间中。我想对于大的堆值,很难在启动后添加到您的地址空间,同时确保它保持连续。你可能需要在启动时获取它,或者根本不获取。因此,它是固定的。
即使它没有立即全部使用,整个堆的地址空间也会在启动时保留。如果它无法为您传递的-Xmx值保留足够大的连续地址空间块,则会启动失败。这就是为什么在32位Windows上分配> 1.4GB堆很困难的原因,因为很难找到那么大或更大的连续地址空间,因为某些DLL喜欢在特定位置加载,从而破碎地址空间。当您进入64位时,这不是真正的问题,因为有更多的地址空间。
这几乎肯定是出于性能原因。我找不到详细说明此问题的好链接,但这是Peter Kessler(full link-一定要阅读评论)的一个相当不错的引用,我在搜索时发现他在Sun的JVM上工作。
我们需要一个连续的内存区域用于堆,是因为我们有一堆侧面的数据结构,它们通过(缩放的)偏移量从堆的起始位置索引。例如,我们使用“卡标记阵列”跟踪对象参考更新,该阵列对应512字节的堆有一个字节。当我们在堆中存储一个参考时,我们必须标记卡标记阵列中相应的字节。我们将存储的目标地址右移并使用它来索引卡标记阵列。在C++中你可以做的有趣地址算术游戏,在Java中你无法做到。这是2004年的情况 - 我不确定现在是否已经改变,但我非常确定它仍然成立。如果您使用类似Process Explorer的工具,您会发现Java应用程序的虚拟大小(添加虚拟大小和专用大小内存列)包括从启动点开始的总堆大小(加上其他所需空间,毫无疑问),即使进程使用的内存在堆开始填充之前也远远达不到那个大小......

8

历史上有一个原因限制了这一点,那就是不允许浏览器中的小程序占用用户的所有内存。微软VM从未有过这样的限制,实际上可以做到这一点,这可能会导致某种形式的拒绝服务攻击针对用户的计算机。只有一年前,Sun在1.6.0更新10 VM中引入了一种方法,让小程序指定他们想要多少内存(限制为一定比例的物理内存),而不是总是将它们限制在64MB,即使在有8GB或更多可用内存的计算机上。

现在,由于JVM已经发展,当VM不在浏览器内运行时,应该可以摆脱这种限制,但是Sun显然从未认为这是一个高优先级的问题,尽管已经提交了无数的错误报告来最终允许堆增长。


1
你说得对,确实有一些错误报告要求这样做:http://bugs.sun.com/view_bug.do?bug_id=4741914,http://bugs.sun.com/view_bug.do?bug_id=4408373。 - Matt McHenry
这是唯一一个对我有意义的答案,包括链接问题的答案。我花了几个小时追踪Crashplan的问题,而简单的解决方法只是增加一个不起眼的设置文件中的不起眼的参数。谁会选择256MB的上限,让我的备份在数周内失败而没有注意到系统内存充足呢? - Lieven Keersmaekers

4
我认为简短而尖刻的答案是因为 Sun 公司认为开发这个功能所需的时间和成本不值得。
在我看来,这种功能最有吸引力的用例是桌面端。然而,在启动 JVM 的机制方面,Java 在桌面端上一直是灾难性的。我怀疑那些最关注这些问题的人往往会专注于服务器端,并认为任何其他细节最好由本地包装器处理。这是一个不幸的决定,但它只应该是选择适合应用程序的正确平台时的决策点之一。

如果你可以增长,一个非常合理的要求是能够缩小,而Sun JVM真的不喜欢放弃内存。Microsoft JVM可以使用系统中的所有内存。 - Thorbjørn Ravn Andersen
@Thorbjørn Ravn Andersen,它可以缩小,尽管这是一种相对较新且文档不完善的能力(也是桌面上缺失的一些非常糟糕的东西)。请参见-XX:MaxHeapFreeRatio和-XX:MinHeapFreeRatio https://dev59.com/J3RA5IYBdhLWcg3w9y50#763305 - Yishai
未记录的XX选项不计入:) - Thorbjørn Ravn Andersen
1
我认为它们已经有文档了,即使是针对Sun的HotSpot VM:http://java.sun.com/javase/technologies/hotspot/vmoptions.jsp - Joshua McKinnon

3

我个人认为这与操作系统上运行的其他应用程序的内存管理有关。

如果您将最大堆大小设置为例如RAM上的数量,您实际上让虚拟机决定它需要多少内存(最高限度为此)。这样做的问题是,虚拟机在决定需要进行垃圾回收之前,可能会占用盒子上的所有内存,从而有效地使其运行的机器变得不稳定。

当您指定最大堆大小时,您向虚拟机发出的信号是,您可以在需要开始垃圾回收之前使用这些内存量。您不能拥有更多,因为如果您占用了更多,则运行在该盒子上的其他应用程序将变慢,并且如果您使用超过此限制,则将开始交换到磁盘。

还要注意,内存有两个相关的值,即“当前堆大小”和“最大堆大小”。当前堆大小是堆当前使用的内存量,如果需要更多,则可以调整堆的大小,但它不能将堆大小调整到超过最大堆大小的值。


我认为这是服务器上的一个很好的答案,因为那里有一个理解如何调整JVM的管理员(或者至少可以合理地期望如此),并设置适当的最大值以便与其他进程良好协作。然而,在桌面上,你要求用户去调整配置文件和启动脚本,这是他们没有经验和理解的事情,而Sun目前的答案是 - 用C语言编写一个启动器。 - Yishai
@Yishai: 太阳虚拟机(Sun VM)的默认值对于大多数应用程序来说是相当合理的,而且据我所知,默认值也会根据可用的内存进行调整。对于那些默认值有问题的应用程序,安装脚本可以很容易地创建一个启动脚本(而不是C语言启动器),它可以设置另外一个最大值。另外,顺便说一下,我也不喜欢这个问题,我确信Sun公司如果愿意的话是可以去掉这个东西的。 - Fredrik
@Fredrik,如果用户在安装脚本运行后向系统添加了内存怎么办?一些桌面应用程序希望在可用时使用更多的RAM(例如用于缓存)。 - Yishai

2
从IBM的性能调优提示(因此可能不直接适用于Sun的虚拟机)中得知。
Java堆参数影响垃圾回收的行为。增加堆大小支持更多的对象创建。因为大堆需要更长的时间来填充,所以应用程序在进行垃圾回收之前运行的时间更长。然而,更大的堆也需要更长的时间来压缩,并且导致垃圾回收需要更长的时间。
JVM有阈值用于管理JVM的存储。当达到这些阈值时,垃圾收集器被调用以释放未使用的存储空间。因此,垃圾回收可能会导致Java性能显着降低。在更改初始和最大堆大小之前,您应考虑以下信息: 在大多数情况下,应将最大JVM堆大小设置为比初始JVM堆大小更高的值。这使得JVM在初始堆的限制范围内在正常、稳定状态期间有效运行,同时在高事务量期间通过扩展堆到最大JVM堆大小来有效运行。在某些罕见的情况下,如果需要绝对最佳性能,则可能希望为初始堆大小和最大堆大小指定相同的值。这将消除JVM需要扩展或收缩JVM堆大小时发生的一些开销。确保区域足够大,以容纳指定的JVM堆。
要注意不要将初始堆大小设置得太大。虽然初始堆大小通过延迟垃圾回收最初提高了性能,但是当垃圾回收最终开始时,大堆大小会影响响应时间,因为收集过程需要更长的时间。
所以,我想你不能在运行时更改值的原因是可能没有帮助:无论你的堆空间是否足够。一旦用尽,将触发GC循环。如果这不能释放空间,那么你就完了。你需要捕获OutOfMemoryException,增加堆大小,然后再次尝试计算,希望这次有足够的内存。
通常,除非你需要它,否则VM不会使用最大堆大小,因此,如果您认为可能需要在运行时扩展内存,则可以指定较大的最大堆大小。
我承认这有点令人不满意,并且似乎有点懒惰,因为我可以想象出一个合理的垃圾回收策略,当GC无法释放足够的空间时会增加堆大小。不过,我的想象力能否转化为高性能的GC实现又是另一回事了 ;)

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