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

Alb*_*ert 12 java memory jvm memory-management

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

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

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

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

  • 例如,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处理内存分配的方式与本机应用程序不同?

Tim*_*one 15

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


ape*_*ins 7

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

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

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

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

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

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