为啥 Java 堆的最大大小是固定的?
Posted
技术标签:
【中文标题】为啥 Java 堆的最大大小是固定的?【英文标题】:Why is the maximum size of the Java heap fixed?为什么 Java 堆的最大大小是固定的? 【发布时间】:2011-01-07 18:09:15 【问题描述】:它is not possible 在VM启动后增加Java堆的最大大小。造成这种情况的技术原因是什么?垃圾收集算法是否依赖于使用固定数量的内存?还是出于安全原因,通过消耗所有可用内存来防止 Java 应用程序对系统上的其他应用程序执行 DOS 操作?
【问题讨论】:
类似问题:“为什么(Sun)JVM 有一个固定的内存使用上限(-Xmx)?” - ***.com/questions/3358328/… 【参考方案1】:我认为简短而尖刻的答案是因为 Sun 认为开发时间和成本不值得。
此类功能最引人注目的用例是在桌面上,IMO,当谈到启动 JVM 的机制时,Java 在桌面上一直是一场灾难。我怀疑那些对这些问题思考最多的人倾向于关注服务器端并查看最好留给本地包装器的任何其他细节。这是一个不幸的决定,但它应该只是决定应用程序的正确平台时的决策点之一。
【讨论】:
如果可以增长,一个非常合理的要求是也可以收缩,Sun JVM 真的不喜欢放弃内存。 Microsoft JVM 可以使用系统中的所有内存。 @Thorbjørn Ravn Andersen,它可以缩小,尽管这是一种最近才出现且记录不充分的能力(而且在桌面上也缺少一些非常糟糕的东西)。请参阅 -XX:MaxHeapFreeRatio 和 -XX:MinHeapFreeRatio ***.com/questions/763295/… 未记录的 XX 选项不计算在内 :) 我认为它们已记录在案,即使特定于 Sun 的 HotSpot VM:java.sun.com/javase/technologies/hotspot/vmoptions.jsp【参考方案2】:我的直觉是,它与操作系统上运行的其他应用程序的内存管理有关。
例如,如果您将最大堆大小设置为机器上的 RAM 量,您可以有效地让 VM 决定它需要多少内存(达到此限制)。这样做的问题是,VM 可以有效地削弱正在运行的机器,因为它会在决定需要进行垃圾收集之前接管机器上的所有内存。
当您指定最大堆大小时,您对虚拟机的意思是,您可以在需要开始垃圾收集之前使用此内存量。你不能拥有更多,因为如果你使用更多,那么在盒子上运行的其他应用程序将会变慢,如果你使用的更多,你将开始交换到磁盘。
还要注意,它们是关于内存的两个值,即“当前堆大小”和“最大堆大小”。当前堆大小是堆大小当前正在使用的内存量,如果需要更多内存,它可以调整堆大小,但不能将堆大小调整到超过最大堆大小的值。
【讨论】:
我认为这在服务器上是一个很好的答案,那里有一个管理员了解如何调整 JVM(或者至少您可以合理地期望这一点)并设置适当的最大值以与其他过程。但是,在桌面上,您要求用户修改配置文件和启动脚本,这是他们没有经验或理解的东西,而 Sun 目前的答案是 - 用 C 编写一个启动器。 @Yishai:Sun VM 默认值对于大多数应用程序来说是相当合理的,iirc 的默认值也会根据可用 RAM 进行调整。对于默认设置有问题的应用程序,安装脚本可以轻松创建一个启动脚本(不是 c 启动器),该脚本设置另一个最大值。编辑:顺便说一句,我也不喜欢它,我相信如果 Sun 愿意,他们可以移除它。 @Fredrik,如果用户在安装脚本运行后将 ram 添加到他们的系统怎么办?一些桌面应用程序希望在可用时使用更多可用 RAM(例如用于缓存)。【参考方案3】:来自 IBM 的 performance tuning tips(因此可能不直接适用于 Sun 的 VM)
Java 堆参数影响垃圾回收的行为。增加堆大小支持更多的对象创建。因为大堆需要更长的时间来填充,所以应用程序在垃圾回收发生之前运行的时间更长。但是,更大的堆也需要更长的时间来压缩并导致垃圾回收时间更长。
JVM 具有用于管理 JVM 存储的阈值。当达到阈值时,将调用垃圾收集器以释放未使用的存储空间。因此,垃圾回收会导致 Java 性能显着下降。在更改初始和最大堆大小之前,您应该考虑以下信息: 在大多数情况下,您应该将最大 JVM 堆大小设置为高于初始 JVM 堆大小的值。这允许 JVM 在初始堆范围内的正常、稳定状态期间有效运行,但也可以通过将堆扩展到最大 JVM 堆大小而在高事务量期间有效运行。在需要绝对最佳性能的极少数情况下,您可能希望为初始堆大小和最大堆大小指定相同的值。这将消除 JVM 需要扩展或收缩 JVM 堆大小时发生的一些开销。确保该区域足够大以容纳指定的 JVM 堆。 当心初始堆大小太大。虽然大堆大小最初会通过延迟垃圾收集来提高性能,但大堆大小最终会影响垃圾收集最终启动时的响应时间,因为收集过程需要更多时间。
所以,我猜你不能在运行时更改值的原因是因为它可能无济于事:要么你的堆中有足够的空间,要么你没有。一旦你用完,就会触发一个 GC 循环。如果这不能释放空间,那么无论如何你都会被塞满。您需要捕获 OutOfMemoryException,增加堆大小,然后重试计算,希望这次您有足够的内存。
通常,除非您需要,否则 VM 不会使用最大堆大小,因此如果您认为可能需要在运行时扩展内存,您可以指定一个较大的最大堆大小。
我承认这有点不令人满意,而且似乎有点懒惰,因为我可以想象一个合理的垃圾收集策略,当 GC 无法释放足够的空间时会增加堆大小。不过,我的想象力能否转化为高性能 GC 实现是另一回事;)
【讨论】:
【参考方案4】:从历史上看,这种限制是有原因的,它不允许浏览器中的小程序占用所有用户的内存。从未有过这种限制的微软虚拟机实际上允许这样做,这可能导致对用户计算机的某种拒绝服务攻击。就在一年前,Sun 在 1.6.0 Update 10 VM 中引入了一种方法,让小程序可以指定它们想要多少内存(限制在物理内存的某个固定份额),而不是总是将它们限制为 64MB,即使在计算机上也是如此有 8GB 或更多可用空间。
现在,由于 JVM 已经发展,当 VM 不在浏览器中运行时,应该可以摆脱这个限制,但 Sun 显然从未将其视为如此高优先级的问题,尽管已经有大量的错误报告被报告归档以最终允许堆增长。
【讨论】:
你是对的,有错误报告要求这样做:bugs.sun.com/view_bug.do?bug_id=4741914, bugs.sun.com/view_bug.do?bug_id=4408373 +1 这是唯一对我有意义的答案,包括链接问题的答案。只需花费数小时来解决 Crashplan 的问题,简单的解决方法就是在晦涩的设置文件中增加一个晦涩的参数。到底谁选择了 256MB 的上限,让我的备份失败了几个星期都没有注意到,并且有足够的系统内存可供使用。【参考方案5】:在 Sun 的 JVM 中,我知道,整个堆必须分配在一个连续的地址空间中。我想对于大堆值,在启动后添加到您的地址空间同时确保它保持连续是非常困难的。您可能需要在启动时获取它,或者根本不需要。因此,它是固定的。
即使没有立即使用,整个堆的地址空间也会在启动时保留。如果它不能为您传递的 -Xmx 的值保留足够大的连续地址空间块,它将无法启动。这就是为什么很难在 32 位 Windows 上分配大于 1.4GB 的堆的原因——因为很难找到该大小或更大的连续地址空间,因为某些 DLL 喜欢在某些地方加载,从而使地址空间碎片化。当您使用 64 位时,这不是一个真正的问题,因为有更多的地址空间。
这几乎肯定是出于性能原因。我找不到一个很好的链接来详细说明这一点,但这是我在搜索时发现的 Peter Kessler (full link - 请务必阅读 cmets) 的一个很好的引用。我相信他在 Sun 从事 JVM 方面的工作。
我们需要连续内存的原因 堆的区域是我们有一个 一堆边数据结构 由(缩放的)偏移量索引 堆的开始。例如,我们 跟踪对象引用更新 一个字节的“卡片标记数组” 对于每 512 字节的堆。什么时候我们 在我们拥有的堆中存储一个引用 标记相应的字节 卡片标记数组。我们右移 商店的目的地地址和 用它来索引卡片标记数组。 有趣的寻址算术游戏 不能在 Java 中做到(有 to :-) 玩 C++。
这是在 2004 年 - 我不确定从那以后发生了什么变化,但我很确定它仍然存在。如果您使用 Process Explorer 之类的工具,您可以看到 Java 应用程序的虚拟大小(添加虚拟大小和私有大小内存列)包括从启动点开始的总堆大小(毫无疑问,加上其他所需空间) ,即使进程“使用”的内存在堆开始填满之前不会接近...
【讨论】:
以上是关于为啥 Java 堆的最大大小是固定的?的主要内容,如果未能解决你的问题,请参考以下文章