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

时间:2010-07-28 23:20:50

标签: java memory jvm

本着问题Java: Why does MaxPermSize exist?的精神,我想问一下为什么Sun JVM对其内存分配池的大小使用固定的上限。

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

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

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

编辑:

一些答案​​指出,这将保护系统的其余部分免受Java程序的影响而导致内存泄漏;没有限制,这会耗尽整个系统耗尽所有内存。这是事实,但对于任何其他程序同样如此,现代操作系统已经允许您限制程序的最大内存(Linux ulimit,Windows“作业对象”)。所以这并没有真正回答这个问题,即“为什么JVM的做法与大多数其他程序/运行时环境不同?”。

8 个答案:

答案 0 :(得分:25)

  

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

原因是 NOT GC需要事先知道最大堆大小是多少。 JVM显然能够将其堆扩展到最大...并且我确信它将是相对小的更改来移除该最大值。 (毕竟,其他Java实现也是这样做的。)同样可以有一种简单的方法来向JVM说“使用尽可能多的内存”。

我确信真正的原因是使用所有可用内存保护主机操作系统免受错误Java应用程序的影响。使用无限堆运行可能很危险。

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

如果默认情况下JVM使用无界堆运行,那么每当有人运行带有存储泄漏的Java程序时......或者只是试图使用太多内存......他们就有可能导致整个操作系统崩溃。

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

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

修改

回应评论:

  • 并不重要为什么Sun的JVM存在于有限堆中,而其他应用程序则不然。他们这样做,这样做的好处是(IMO)清楚。也许一个更有趣的问题是,为什么其他托管语言默认情况下不会绑定他们的堆。

  • -Xmxulimit方法在质量上有所不同。在前一种情况下,JVM完全了解它运行的限制,并有机会相应地管理其内存使用情况。在后一种情况下,典型的C应用程序首先知道的是malloc调用失败。典型的响应是退出错误代码(如果程序检查malloc结果),或者死于分段错误。好的,C应用程序理论上可以跟踪它使用了多少内存,并尝试应对即将发生的内存危机。但这将是一项艰苦的工作。

  • Java和C / C ++应用程序的另一个不同之处在于,前者往往更复杂,运行时间更长。实际上,这意味着Java应用程序更容易遭受缓慢泄漏。在C / C ++案例中,内存管理更难的事实意味着开发人员不会尝试构建具有该复杂性的单个应用程序。相反,他们更有可能通过让一个监听器处理子进程的fork来做东西......然后退出来构建(比方说)一个复杂的服务。这自然减轻了子进程中内存泄漏的影响。

  • JVM响应“自适应”响应来自操作系统的请求以回忆内存的想法很有意思。但是存在一个很大的问题。为了返回一段内存,JVM首先必须清除段中的任何可到达对象。通常这意味着运行垃圾收集器。但是,如果系统处于内存危机中,那么运行垃圾收集器就是你要做的最后事情...因为它几乎可以保证产生一连串的虚拟内存分页。

答案 1 :(得分:6)

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

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

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

Sun可能已经省略了该功能,并且只是告诉人们使用OS本机资源限制机制,但他们可能希望始终有限制,因此他们自己实现了。 无论如何,JVM需要了解任何此类限制(以适应其GC策略),因此使用OS本机机制不会节省太多编程工作。

此外,有一个原因可以解释为什么这样的内置限制对于JVM比没有GC的“普通”程序(例如C / C ++程序)更重要:

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

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

答案 2 :(得分:3)

我认为它的一部分与垃圾收集器(GC)的实现有关。 GC通常是惰性的,这意味着当堆处于最大大小时,它只会启动真正尝试在内部回收内存。如果您没有设置上限,运行时将很乐意继续膨胀,直到它使用您系统上的每个可用内存位。

这是因为从应用程序的角度来看,获取更多资源比使用已经拥有的资源来充分利用更高效。这往往对Java的许多(如果不是大多数)使用有意义,Java是一种服务器设置,其中应用程序实际上是服务器上唯一重要的事情。当您尝试使用Java实现客户端时,它往往略微不太理想,它将同时在许多其他应用程序中运行。

请记住,对于本机程序,程序员通常会请求但也会明确地清理资源。对于进行自动内存管理的环境,这通常不正确。

答案 3 :(得分:2)

这是由于JVM的设计。其他JVM(如微软和一些IBM的JVM)可以根据需要使用系统中可用的所有内存,没有任意限制。

我相信它允许进行GC优化。

答案 4 :(得分:1)

我认为内存的上限与JVM是VM的事实有关。

由于任何物理机器都有一个给定(固定)的RAM,因此VM有一个。

最大大小使操作系统更容易管理JVM,并确保一些性能提升(交换更少)。

Sun'JVM也适用于非常有限的硬件架构(嵌入式ARM系统),资源管理至关重要。

答案 5 :(得分:0)

上面没有人给出的一个答案是JVM使用堆和非堆内存池。在堆上设置上限不仅定义了堆内存池可用的内存量,还定义了可用于NON-HEAP使用的内存量。我想JVM可以只在虚拟内存的顶部分配非堆,并在虚拟内存的底部堆积,并相互增长。

非堆内存包括构成JVM的DLL或SO以及正在使用的任何本机代码以及已编译的Java代码,线程堆栈,本机对象,PermGen(有关已编译类的元数据)以及其他用途。我已经看到Java程序崩溃了,因为应用程序耗尽非堆内存的堆已经给了很多内存。这是我了解到,通过不将堆设置得太大来为非堆使用保留内存非常重要。

这在32位世界中产生了更大的差异,当然,应用程序通常只有2GB的虚拟地址空间,而不是64位的世界。

答案 6 :(得分:-1)

它根据需要分配内存,最多为-Xmx;)

我能想到的一个原因是,一旦JVM为其堆分配了一定量的内存,它就永远不会让它消失。因此,如果您的堆没有上限,JVM可能只是获取系统上的所有可用内存,然后永远不会让它继续运行。

上限还告诉JVM什么时候需要进行完整的垃圾回收。如果您的应用程序仍处于上限之下,JVM将推迟垃圾收集并让应用程序的内存占用量增加。

原生程序也可能由于内存不足错误而死亡,因为本机应用程序也有内存限制:系统上可用的内存 - 其他应用程序已经拥有的内存。

JVM还需要一个连续的系统内存块,以便有效地执行垃圾收集。

修改

Contiguous memory claimhere

JVM显然会释放一些内存,但默认配置为rare

答案 7 :(得分:-1)

将触发GC的上限和可分配的最大值分开是否更有意义?一旦分配的内存达到上限,GC就可以启动并释放一些内存到空闲池。 有点像我清理我的办公桌,我与同事分享。我有一张大桌子,我在桌子上可以容忍多少垃圾的门槛远低于我办公桌的大小。在垃圾收集之前,我不需要填满每一英寸。

我还可以将我使用的一些桌面空间返回给我的同事,他正在共享我的桌面....我明白jvms在将它们分配给自己之后不会将内存返回给系统,但它不一定是那样的吗?