为什么(Sun)JVM具有固定的内存使用上限(-Xmx)?
本着Java的精神:为什么MaxPermSize存在? ,我想问问为什么Sun JVM使用内存分配池的大小的固定上限。
默认是你的物理RAM的1/4(有上限和下限); 因此,如果您的应用程序需要内存,您必须手动更改限制(参数-Xmx),否则您的应用程序将执行得很差,甚至可能会因为OutOfMemoryError而崩溃。
为什么这个固定的限制甚至存在? 为什么JVM不能根据需要分配内存,就像本地程序在大多数操作系统上一样?
这将解决Java软件的一整套常见问题(只要谷歌,看网上有多less提示,通过设置-Xmx来解决问题)。
编辑:
有些答案指出,这将保护系统的其他部分免受Java程序的内存泄漏; 没有限制,这会耗尽所有内存,从而降低整个系统的性能。 但是,对于其他程序也是如此,现代操作系统已经允许您限制程序的最大内存(Linux ulimit,Windows“Job Objects”)。 所以这并没有真正回答这个问题,即“为什么JVM与大多数其他程序/运行时环境不一样?”。
为什么这个固定的限制甚至存在? 为什么JVM不能根据需要分配内存,就像本地程序在大多数操作系统上一样?
原因并不在于GC需要事先知道最大堆大小是多less。 JVM显然能够扩展它的堆…达到最大限度…我相信这将是一个相对较小的变化,以消除最大限度的。 (毕竟,其他的Java实现都是这样做的。)同样可以有一个简单的方法来向JVM说“尽可能多地使用内存”。
我确信真正的原因是使用所有可用的内存保护主机操作系统免受故障Java应用程序的影响。 以无限堆运行是潜在的危险。
基本上,如果某些应用程序尝试使用所有可用的内存,许多操作系统(例如Windows,Linux)会遭受严重的性能下降。 例如,在Linux上,系统可能会发生严重的瘫痪,导致系统上的所有内容都运行得非常慢。 在最坏的情况下,系统将无法启动新的进程,并且当操作系统拒绝他们(合法)的更多内存请求时,现有进程可能会开始崩溃。 通常,唯一的select是重新启动。
如果JVM在缺省情况下使用无限堆运行,任何时候有人运行带有存储漏洞的Java程序,或者只是试图使用太多的内存……他们将冒风险将整个操作系统closures。
总之,有一个默认的堆绑定是一件好事,因为:
- 它保护你的系统的健康,
- 它鼓励开发人员/用户通过“饥饿的”应用程序来考虑内存使用情况
- 它可能允许GC优化。 (正如其他答案所build议的那样:这似乎是合理的,但我不能证实这一点。)
编辑
回应评论:
-
为什么Sun的JVM生活在一个有限的堆中,而其他应用程序却没有。 他们这样做,而且这样做的好处是(海事组织)明确。 也许一个更有趣的问题是,为什么其他的托pipe语言默认情况下不会在堆上绑定。
-
-Xmx
和ulimit
方法在性质上是不同的。 在前一种情况下,JVM完全了解它正在运行的限制,并有机会相应地pipe理其内存使用情况。 在后一种情况下,典型的C应用程序知道的第一件事是malloc
调用失败。 典型的响应是用一个错误代码(如果程序检查malloc
结果)退出,或者由于分段错误而退出。 好的,一个C应用程序理论上可以跟踪它使用了多less内存,并尝试回应即将发生的内存危机。 但这将是艰苦的工作。 -
另一个与Java和C / C ++应用程序不同的地方是,前者往往更复杂,运行时间更长。 实际上,这意味着Java应用程序更容易受到缓慢泄漏的影响。 在C / C ++的情况下,内存pipe理比较困难的事实意味着开发人员不会尝试构build这种复杂性的单一应用程序。 相反,他们更可能build立(说)一个复杂的服务,通过让一个监听程序处理subprocess的fork来执行任务…然后退出。 这自然地减轻了subprocess中内存泄漏的影响。
-
JVM响应“自适应”来自操作系统的请求来回忆内存的想法是有趣的。 但是有一个大问题。 为了提供一段内存,JVM首先必须清除段中的任何可到达的对象。 通常这意味着运行垃圾收集器。 但是,如果系统处于内存危机中,运行垃圾收集器是您想要做的最后一件事情,因为它几乎可以保证产生一阵虚拟内存分页。
嗯,我会尽量总结答案。
没有技术上的原因,为什么JVM需要有一个硬堆栈大小的限制。 它本来可以没有一个实现,事实上许多其他的dynamic语言没有这个。
因此,给JVM一个堆大小限制只是实现者的devise决定。 猜测为什么这样做是有点困难,可能没有一个单一的原因。 最可能的原因是它有助于保护系统免受Java程序的内存泄漏,否则可能会耗尽所有内存,导致其他应用程序崩溃或系统瘫痪。
Sun可能已经忽略了这个特性,只是告诉人们使用操作系统原生的资源限制机制,但是他们可能总想有一个限制,所以他们自己实现了。 无论如何,JVM需要意识到任何这样的限制(以适应其GC策略),所以使用OS本机机制不会节省许多编程工作。
另外,为什么这样的内置限制对于JVM比对于没有GC的“正常”程序更重要(比如C / C ++程序)是有原因的:
与手动内存pipe理的程序不同,即使使用固定的input数据,使用GC的程序也没有真正的内存需求。 它只有一个最小的要求,即在一个给定的时间点,所有实际存在(可达)的对象的大小之和。 但是,在实际中,程序需要额外的内存来保存已死的但尚未GCed的对象,因为GC不能立即收集每个对象,因为这会导致太多的GC开销。 所以GC只是时不时的,因此堆上需要一些“呼吸室”,死者可以等待GC。
这意味着使用GC的程序所需的内存实际上是保存内存和获得良好性能(通过减lessGC运行频率)之间的折衷。 因此,在某些情况下,将堆的限制设置为低于JVM将使用的堆的限制是有意义的,因此可以节省内存而牺牲性能。 要做到这一点,需要有一种方法来设置堆的限制。
我认为它的一部分与垃圾收集器(GC)的实施有关。 GC通常是懒惰的,这意味着只有当堆处于最大尺寸时才真正开始尝试回收内存。 如果你没有设置上限,运行时就会继续膨胀,直到它使用系统上的所有可用内存位。
这是因为从应用程序的angular度来看,获取更多资源比使用已有的资源充分利用资源要更高效。 这对于很多(如果不是最多的话)Java使用来说都是有意义的,这是一个服务器设置,应用程序在这里是服务器上唯一重要的东西。 当你试图用Java实现一个客户端时,它往往会稍微不理想,它将同时在几十个其他应用程序中运行。
请记住,对于本机程序,程序员通常会请求,但也明确地清理资源。 对于进行自动内存pipe理的环境来说,情况并非如此。
这是由于JVM的devise。 其他JVM(如Microsoft和IBM的一些JVM)可以根据需要使用系统中可用的所有内存,而不受任何限制。
我相信它可以进行GC优化。
我认为内存的上限与JVM是虚拟机的事实有关。
由于任何物理机器都有一个给定的(固定的)内存,所以虚拟机有一个。
最大的大小使得操作系统更易于pipe理JVM,并确保一些性能提升(交换较less)。
Sun公司的JVM也采用相当有限的硬件架构(embedded式ARM系统),而资源pipe理至关重要。
分离触发GC的上限和可以分配的最大值是不是更有意义? 一旦分配的内存达到上限,GC可以踢入并释放一些内存到空闲池。 有点像我打扫我的桌子,我与我的同事分享。 我有一张大桌子,而我在桌子上能容忍多less垃圾的门槛比我办公桌的大小要小得多。 垃圾收集之前,我不需要填满每一个可用的英寸。
我还可以将我使用的一些桌面空间归还给我的同事,他们正在共享我的桌面….我知道,jvms在将内存分配给自己之后不会将内存返还给系统,但是,不一定是这样吗?
上面没有人给出的答案是JVM使用堆和非堆内存池。 在堆上设置一个上限不仅定义了堆内存池有多less内存,还定义了多less内存可用于非堆用法。 我猜想JVM可以在虚拟内存的顶部分配非堆,并且在虚拟内存的底部堆积并且相互增长。
非堆内存包括构成JVM的DLL或SO,以及使用的任何本地代码以及编译的Java代码,线程堆栈,本地对象,PermGen(关于编译类的元数据)以及其他用途。 我见过Java程序崩溃,因为有太多的内存给了应用程序用完非堆内存的堆。 这是我了解到,通过不设置堆太大来为非堆用途预留内存是非常重要的。
当然,在32位的世界里,这个应用程序通常只有2GB的虚拟地址空间,这比64位的世界更大。
它根据需要分配内存,高达-Xmx;)
我能想到的一个原因是,一旦JVM为它的堆分配了大量的内存,就永远不会放过它。 所以如果你的堆没有上限,JVM可能只是抓住系统中所有的空闲内存,然后永远不会放过它。
上限还告诉JVM什么时候需要做一个完整的垃圾回收。 如果您的应用程序仍处于上限之下,则JVM将延迟垃圾回收,并让应用程序的内存占用量增加。
原生程序也可能由于内存不足错误而死亡,因为原生应用程序也具有内存限制:系统上可用的内存 – 已被其他应用程序占用的内存。
JVM还需要连续的系统内存块才能有效执行垃圾收集。
编辑
连续的内存索赔或在这里
JVM显然会让一些内存去,但在默认configuration下很less见 。
Java只是其中之一,对于服务器来说真的很好,而且对桌面系统来说真的很糟糕。 真棒,你可以给它一个服务器上的最大和最小的内存量,如果它只是增长,直到开始分页到磁盘,JVM将失去大量的权力。 但是,这使得在许多不同的机器上运行的桌面应用程序真的很糟糕:)