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

时间:2010-08-16 19:11:12

标签: java memory memory-management jvm

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

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

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

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

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

为什么它不仅仅使用mallocfree等基础系统技术?或者,如果他们不喜欢libc实现,他们可以使用jemalloc(如FreeBSD和Firefox),这似乎相当不错。我很确定这会比JVM内存池表现更好。而且不仅表现更好,还需要更少的内存,尤其是适用于小型应用。


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

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

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


编辑:一个非常相关的问题:Why does the (Sun) JVM have a fixed upper limit for memory usage?或换句话说:为什么JVM处理内存分配的方式与本机应用程序不同?

7 个答案:

答案 0 :(得分:15)

您需要记住,垃圾收集器不仅仅是收集无法访问的对象。它还优化堆空间并跟踪exactly where there is memory available以分配用于创建新对象。

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

  

快速路径分配不会调用   进入JVM来分配一个对象。   JIT编译器知道如何分配   走出了年轻一代和代码   分配是在线生成的   用于对象分配。口译员   也知道如何进行分配   没有打电话给VM。

请注意,JVM也不遗余力地尝试获取大型连续内存块,这些内存块可能有their own performance benefits(请参阅“丢失缓存的成本”)。我想,对malloc(或替代方案)的调用在调用之间提供连续内存的可能性有限,但也许我错过了那些内容。

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

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

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

  

通常,他们消费越来越多   内存超过运行时间。

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

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

*我不知道这是否实际上是唯一的时间以及从操作系统的角度来看这是如何观察的,但是the documentation says它是“最大百分比” GC后自由堆积到避免收缩,“这似乎表明了这一点。

  

即使是一些非常小的示例演示   应用程序加载了大量的   存储器中。

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

  

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

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

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

答案 1 :(得分:7)

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

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

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

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

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

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

答案 2 :(得分:3)

回答你问题的一部分;

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

一旦达到最大大小,Java将不再从操作系统请求更多RAM,即使有很多空闲。如果您尝试创建更多对象,则不会留下堆空间,并且您将获得OutOfMemory异常。 现在,如果您正在查看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,也会引发OutOfMemory异常。

我希望这是有道理的......


编辑:

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

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

答案 3 :(得分:2)

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

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

答案 4 :(得分:2)

  

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

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

  

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

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

询问Java为何不使用mallocfree的主要问题归结为目标市场问题。 Java旨在消除开发人员对内存管理的头痛。 Java的垃圾收集器在释放内存时可以很好地释放内存,但Java在内存限制的情况下并不意味着可以与C ++竞争。 Java做了它打算做的事情(删除开发人员级别的内存管理),JVM很好地承担了责任,足以满足大多数应用程序的要求。

答案 5 :(得分:2)

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

答案 6 :(得分:0)

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

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

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

有关此复杂主题的详细信息,请参阅Tuning Garbage Collection