为什么(Oracle)JVM有固定的内存使用上限(-Xmx)?

2022-08-31 22:35:50

本着对Java:为什么存在MaxPermSize?的精神,我想问为什么Oracle JVM对其内存分配池的大小使用固定的上限。

默认值为物理RAM的1/4(上限和下限);因此,如果您有一个内存密集型应用程序,则必须手动更改限制(参数-Xmx),否则您的应用程序将表现不佳,甚至可能因OutOfMemoryError而崩溃。

为什么这个固定的限制甚至存在?为什么 JVM 不像大多数操作系统上的本机程序那样根据需要分配内存?

这将解决Java软件的一整类常见问题(只需Google查看网络上有多少提示通过设置-Xmx来解决问题)。

编辑:

一些答案指出,这将保护系统的其余部分免受Java程序的失控内存泄漏;如果没有限制,这将通过耗尽所有内存来使整个系统崩溃。这是事实。但是,对于任何其他程序也是如此,现代操作系统已经允许您限制程序的最大内存(Linux ulimit,Windows“Job Objects”)。因此,这并没有真正回答这个问题,即“为什么JVM与大多数其他程序/运行时环境不同?


答案 1

为什么这个固定的限制甚至存在?为什么 JVM 不像大多数操作系统上的本机程序那样根据需要分配内存?

原因不是GC需要事先知道最大堆大小是多少。JVM显然能够扩展其堆...最大 ...我敢肯定,删除该最大值将是一个相对较小的更改。(毕竟,其他Java实现可以做到这一点。同样有可能有一个简单的方法对JVM说“使用尽可能多的内存”。

我确信真正的原因是使用所有可用内存来保护主机操作系统免受故障Java应用程序的影响。使用未绑定的堆运行具有潜在危险。

基本上,如果某些应用程序尝试使用所有可用内存,许多操作系统(例如Windows,Linux)会遭受严重的性能下降。例如,在Linux上,系统可能会严重崩溃,导致系统上的所有内容都运行得非常慢。在最坏的情况下,系统将无法启动新进程,并且当操作系统拒绝其(合法)请求以获取更多内存时,现有进程可能会开始崩溃。通常,唯一的选择是重新启动。

如果JVM默认使用无界堆运行,那么每当有人运行具有存储泄漏的Java程序时...或者只是试图使用太多的内存...他们可能会关闭整个操作系统。

总之,绑定默认堆是一件好事,因为:

  • 它保护您的系统的健康,
  • 它鼓励开发人员/用户考虑“饥饿”应用程序的内存使用情况,以及
  • 它可能允许GC优化。(正如其他答案所建议的那样:这是有道理的,但我无法证实这一点。

编辑

针对评论:

  • 为什么 Sun 的 JVM 位于有界堆中并不重要,而其他应用程序则不然。他们这样做,这样做的好处是显而易见的(IMO)。也许一个更有趣的问题是为什么其他托管语言默认情况下对其堆进行限制。

  • 和 方法在性质上是不同的。在前一种情况下,JVM 完全了解它正在运行的限制,并有机会相应地管理其内存使用情况。在后一种情况下,典型的 C 应用程序知道的第一件事是调用失败时。典型的响应是使用错误代码退出(如果程序检查结果),或者因分段错误而死亡。好吧,从理论上讲,C应用程序可以跟踪它使用了多少内存,并尝试响应即将到来的内存危机。但这将是一项艰苦的工作。-Xmxulimitmallocmalloc

  • Java和C/C++应用程序的另一个不同之处在于,前者往往更复杂,运行时间更长。在实践中,这意味着Java应用程序更有可能遭受缓慢泄漏的影响。在 C/C++的情况下,内存管理更加困难的事实意味着开发人员不会尝试构建这种复杂性的单个应用程序。相反,他们更有可能通过让子进程的侦听器进程分支来做事情来构建(比如说)一个复杂的服务......,然后退出。这自然会减轻子进程中内存泄漏的影响。

  • JVM“自适应地”响应来自操作系统的请求以返回内存的想法很有趣。但是有一个很大的问题。为了返回一段内存,JVM 首先必须清除该段中的任何可访问对象。通常,这意味着运行垃圾回收器。但是,如果系统处于内存危机中,运行垃圾回收器是您要做的最后一件事......因为它几乎可以保证生成虚拟内存分页的突发。


答案 2

嗯,我会尝试总结到目前为止的答案。

没有技术原因说明为什么 JVM 需要对其堆大小进行硬性限制。它可以在没有一个的情况下实现,事实上许多其他动态语言都没有这个。

因此,为 JVM 设置堆大小限制只是实现者的设计决策。猜测为什么这样做有点困难,可能没有一个原因。最可能的原因是它有助于保护系统免受具有内存泄漏的Java程序的影响,否则可能会耗尽所有RAM并导致其他应用程序崩溃或系统崩溃。

Sun本可以省略这个功能,只是告诉人们使用操作系统原生的资源限制机制,但他们可能希望总是有一个限制,所以他们自己实现了它。无论如何,JVM需要意识到任何此类限制(以调整其GC策略),因此使用操作系统本机机制不会节省太多编程工作。

此外,对于JVM来说,这种内置限制比没有GC的“正常”程序(例如C / C++程序)更重要,还有一个原因:

与具有手动内存管理的程序不同,使用GC的程序实际上没有明确定义的内存要求,即使具有固定的输入数据也是如此。它只有一个最低要求,即在给定时间点实际处于活动状态(可访问)的所有对象的大小之和。但是,在实践中,程序将需要额外的内存来保存死的,但尚未GCed的对象,因为GC无法立即收集每个对象,因为这会导致过多的GC开销。因此,GC只会不时启动,因此堆上需要一些“喘息空间”,死物可以等待GC。

这意味着使用GC的程序所需的内存实际上是节省内存和良好吞吐量(通过让GC运行频率降低)之间的折衷。因此,在某些情况下,如果可以的话,将堆限制设置为低于JVM将使用的堆限制可能是有意义的,因此以牺牲性能为代价来节省RAM。为此,需要有一种方法来设置堆限制。