Java:为什么它使用固定数量的内存? 或者它如何管理记忆?

似乎JVM使用了一些固定数量的内存。 至少我经常看到参数-Xmx (对于最大尺寸)和-Xms (对于初始尺寸),这表明了这一点。

我觉得Java应用程序不能很好地处理内存。 我注意到的一些事情:

  • 甚至一些非常小的示例演示应用程序也会加载大量内存。 也许这是因为加载了Java库。 但为什么需要为每个Java实例加载库? (这似乎是因为多个小应用程序线性地占用更多内存。请参阅此处了解我描述此问题的一些细节。)或者为什么这样做?

  • 像Eclipse这样的大型Java应用程序经常会因一些OutOfMemoryexception而崩溃。 这总是很奇怪,因为我的系统上仍然有足够的内存。 通常,它们会在运行时消耗越来越多的内存。 我不确定他们是否有一些内存泄漏,或者这是因为内存池中的碎片 – 我觉得后者就是这种情况。

  • 例如,Java库似乎需要比类似强大的库(如Qt)更多的内存。 为什么是这样? (比较,启动一些Qt应用程序并查看它们的内存使用情况并启动一些Java应用程序。)

为什么它不使用像mallocfree这样的底层系统技术? 或者如果他们不喜欢libc实现,他们可以使用jemalloc (就像在FreeBSD和Firefox中一样 ),这看起来非常好。 我很确定这会比JVM内存池表现更好。 而且不仅表现更好,还需要更少的内存,尤其是 适用于小型应用。


另外:有人已经尝试过吗? 我会对基于LLVM的Java JIT编译器感兴趣,它只使用malloc / free进行内存处理。

或者这也可能与JVM实现到实现有所不同? 我主要使用Sun JVM。

(另请注意:我不是直接在这里谈论GC。GC只负责计算可以删除的对象和初始化内存释放,但实际的释放是一个不同的子系统.Afaik,它是一些自己的内存池实施,而不仅仅是free电话。)


编辑:一个非常相关的问题: 为什么(Sun)JVM对内存使用有固定的上限? 或者换句话说:为什么JVM处理内存分配的方式与本机应用程序不同?

您需要记住,垃圾收集器不仅仅是收集无法访问的对象。 它还优化堆空间并跟踪可用于为创建新对象分配的内存的确切位置 。

立即知道有空闲内存的位置使新对象分配到年轻代有效,并且防止需要来回运行到底层操作系统。 根据Sun的Jon Masamitsu的说法,JIT编译器还优化了JVM层之外的这种分配:

快速路径分配不会调用JVM来分配对象。 JIT编译器知道如何分配年轻代,并且为对象分配在线生成分配代码。 解释器还知道如何在不调用VM的情况下进行分配。

请注意,JVM也不遗余力地尝试获取大型连续内存块,这些内存块可能具有自己的性能优势 (请参阅“丢失缓存的成本”)。 我认为对malloc (或替代方案)的调用在调用时提供连续内存的可能性有限,但也许我错过了那些东西。

此外,通过维护内存本身,垃圾收集器可以根据使用情况和访问模式进行分配优化。 现在,我不知道它在多大程度上做到这一点,但鉴于这个概念已经注册了Sun 专利 ,我想他们已经用它做了一些事情。

保持分配这些内存块也为Java程序提供了保护。 由于垃圾收集对程序员是隐藏的,所以他们无法告诉JVM“不,保留那些内存;我已经完成了这些对象,但我需要新的空间。” 通过保留内存,GC不会有放弃内存的风险,它将无法返回。 当然,你总是可以得到一个OutOfMemoryException ,但是每次你完成一个对象时,不必毫不费力地将内存返回给操作系统似乎更合理,因为你已经为自己解决了问题。

除此之外,我将尝试直接解决您的一些意见:

通常,它们会在运行时消耗越来越多的内存。

假设这不仅仅是程序正在做的事情(无论出于何种原因,也许它有泄漏,也许它必须跟踪越来越多的数据),我想它与免费哈希空间比率有关(Sun / Oracle)JVM设置的默认值。 -XX:MinHeapFreeRatio的默认值为40%,而-XX:MaxHeapFreeRatio为70%。 这意味着只要剩余40%的堆空间,堆就会通过从操作系统声明更多内存来resize(假设这不会超过-Xmx )。 相反,如果可用空间超过70%,它只会将堆内存释放回操作系统。

考虑如果我在Eclipse中运行内存密集型操作会发生什么; 例如,分析。 我的内存消耗量将会上升,并在整个过程中调整堆大小(可能是多次)。 一旦我完成了,内存需求就会下降,但是到目前为止,70%的堆都是免费的。 这意味着现在分配了大量未充分利用的空间,JVM无意释放。 这是一个主要缺点,但您可以通过自定义您的情况的百分比来解决它。 为了更好地了解这一点,您应该对应用程序进行概要分析,以便查看已使用和已分配的堆空间。 我个人使用YourKit ,但有很多好的选择可供选择。

*我不知道这是否是实际上唯一的时间以及如何从操作系统的角度观察到这一点,但是文档说它是“GC之后释放堆的最大百分比以避免收缩 ”,这似乎表明了这一点。

甚至一些非常小的示例演示应用程序也会加载大量内存。

我想这取决于它们是什么类型的应用程序。 我觉得Java GUI应用程序运行内存很重,但我没有任何证据。 你有一个我们可以看到的具体例子吗?

但为什么需要为每个Java实例加载库?

那么,如果不创建新的JVM进程,您将如何处理加载多个Java应用程序? 过程的隔离是一件好事,这意味着独立加载。 不过,我认为这对于一般的流程并不常见。

最后一点,您在另一个问题中询问的缓慢启动时间可能来自几个初始堆重新分配以达到基线应用程序内存要求(由于-Xms-XX:MinHeapFreeRatio ),具体取决于默认值与你的JVM。

Java在虚拟机中运行,它限制了它的许多部分行为。 请注意术语“虚拟机”。 它实际上就像机器是一个独立的实体一样运行,底层的机器/操作系统只是资源。 -Xmx值定义VM将具有的最大内存量,而-Xms定义应用程序可用的起始内存。

VM是二进制系统不可知的产物 – 这是一个用于允许字节代码在任何地方执行的解决方案。 这类似于模拟器 – 比如旧游戏系统。 它模仿游戏运行的“机器”。

您遇到OutOfMemoryException的原因是因为虚拟机已达到-Xmx限制 – 它实际上已耗尽内存。

就较小的程序而言,它们通常需要更大比例的内存用于VM。 此外,Java有一个默认的起始-Xmx和-Xms(我不记得它们现在是什么),它总是以它开头。 当您开始构建和运行“真正的”应用程序时,VM和库的开销变得不那么明显。

与QT等相关的记忆论证是正确的,但不是全部。 虽然它使用的内存比其中一些内存多,但它们是针对特定体系结构编译的。 自从我使用QT或类似的库以来已经有一段时间了,但是我记得内存管理不是很强大,而且C / C ++程序中的内存泄漏现在仍然很常见。 关于垃圾收集的好处是它消除了导致内存泄漏的许多常见“问题”。 (注意:不是全部。在Java中泄漏内存仍然很可能,只是有点困难)。

希望这有助于消除您可能遇到的一些困惑。

回答你问题的一部分;

启动时Java分配内存的“堆”或固定大小的块(-Xms参数)。 它实际上并没有立即使用所有这些内存,但它告诉操作系统“我想要这么多内存”。 然后,当您创建对象并在Java环境中工作时,它会将创建的对象放入此预分配内存堆中。 如果该内存块已满,则它将从OS请求更多内存,直到达到“最大堆大小”(-Xmx参数)。

一旦达到最大大小,Java将不再从操作系统请求更多RAM,即使有很多空闲。 如果您尝试创建更多对象,则不会留下堆空间,并且您将获得OutOfMemoryexception。 现在,如果您正在查看Windows任务管理器或类似的东西,您将看到使用X兆内存的“java.exe”。 那种排序对应于它为堆请求的内存量,而不是堆内使用的内存量。

换句话说,我可以编写应用程序:

 class myfirstjavaprog { public static void main(String args[]) { System.out.println("Hello World!"); } } 

这基本上只需要很少的记忆。 但如果我用cmd行运行它:

 java.exe myfirstjavaprog -Xms 1024M 

然后在启动时java将立即向操作系统询问1,024 MB的ram,这就是Windows任务管理器中显示的内容。 实际上,该ram没有被使用,但java保留它供以后使用。

相反,如果我有一个试图创建10,000字节大数组的应用程序:

 class myfirstjavaprog { public static void main(String args[]) { byte[] myArray = new byte[10000]; } } 

但是用命令行运行它:

 java.exe myfirstjavaprog -Xms 100 -Xmx 100 

然后Java只能放置100个字节的内存。 由于10,000字节的数组不适合100字节的堆,因此即使操作系统有足够的RAM,也会引发OutOfMemoryexception。

我希望这是有道理的…


编辑:

回到“为什么Java使用如此多的内存”; 为什么你认为它使用了大量的内存? 如果您正在查看操作系统报告的内容,那么这不是它实际使用的内容,它只是保留使用的内容。 如果你想知道java实际使用了什么,那么你可以进行堆转储并浏览堆中的每个对象,看看它使用了多少内存。

回答“为什么不让操作系统处理它?”,我想这对于那些设计它的人来说只是一个基本的Java问题。 我看待它的方式; Java在JVM中运行,JVM是一个虚拟机。 如果您创建VMWare实例或只是系统的任何其他“虚拟化”,您通常必须指定虚拟系统将/可以消耗多少内存。 我认为JVM是相似的。 此外,这种抽象的内存模型让不同操作系统的JVM都以类似的方式运行。 因此,例如Linux和Windows具有不同的RAM分配模型,但JVM可以将其抽象出来并遵循不同操作系统的相同内存使用情况。

Java确实使用mallocfree ,或者至少可以使用JVM的实现。 但由于Java跟踪分配和垃圾收集无法访问的对象,因此它们绝对不够。

至于你的其他文字,我不确定那里是否有问题。

甚至一些非常小的示例演示应用程序也会加载大量内存。 也许这是因为加载了Java库。 但为什么需要为每个Java实例加载库? (这似乎是因为多个小应用程序线性地占用更多内存。请参阅此处了解我描述此问题的一些细节。)或者为什么这样做?

这可能是由于启动和运行JVM的开销

像Eclipse这样的大型Java应用程序经常会因一些OutOfMemoryexception而崩溃。 这总是很奇怪,因为我的系统上仍然有足够的内存。 通常,它们会在运行时消耗越来越多的内存。 我不确定他们是否有一些内存泄漏,或者这是因为内存池中的碎片 – 我觉得后者就是这种情况。

我不完全确定你的意思是“经常崩溃”,因为我认为这在很长一段时间里都没有发生过。 如果是,则可能是由于您之前提到的“最大尺寸”设置。

你问及为什么Java不使用mallocfree主要问题归结为目标市场问题。 Java旨在消除开发人员对内存管理的头痛。 Java的垃圾收集器在释放内存时可以很好地释放内存,但Java在内存限制的情况下并不意味着可以与C ++相媲美。 Java做了它想要做的事情(删除开发人员级别的内存管理),JVM很好地承担了责任,对大多数应用程序来说它已经足够好了。

限制是Sun公司经过深思熟虑的设计决定。 我已经看过至少另外两个没有这种设计的JVM – 微软的那个和IBM的非PC AS / 400系统。 两者都根据需要使用尽可能多的内存增长。

Java不使用固定大小的内存,它总是在-Xms到-Xmx的范围内。

如果Eclipse与OutOfMemoryError崩溃,则需要的内存多于-Xmx授予的内存(配置问题)。

Java不能使用malloc / free(用于创建对象),因为它的内存处理因垃圾收集(GC)而大不相同。 GC会自动删除未使用的对象,与负责内存管理相比,这是一个好处。

有关此复杂主题的详细信息,请参阅调整垃圾收集