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

2022-09-04 06:36:36

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

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

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

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

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

为什么它不只使用底层系统技术,如 和 ?或者,如果他们不喜欢 libc 的实现,他们可以使用 jemalloc(如 FreeBSD 和 Firefox),这似乎相当不错。我很确定这会比JVM内存池表现得更好。不仅性能更好,而且需要更少的内存,特别是对于小型应用程序。mallocfree


补充:有人已经尝试过了吗?我对基于LLVM的JavaJIT编译器非常感兴趣,它只使用/用于内存处理。mallocfree

或者,这也可能因JVM实现而异?我主要使用Sun JVM。

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


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


答案 1

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

立即知道哪里有可用内存,可以有效地将新对象分配给年轻一代,并防止了来回运行到底层操作系统的需要。JIT编译器还优化了远离JVM层的此类分配,根据Sun的Jon Masamitsu的说法:

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

请注意,JVM 不遗余力地尝试获得大型连续内存块,这可能有自己的性能优势(请参阅“缺少缓存的代价”)。我想对(或替代方案)的呼叫在呼叫之间提供连续记忆的可能性有限,但也许我错过了一些东西。malloc

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

保持这些内存块的分配也为 Java 程序提供了保护。由于垃圾回收对程序员是隐藏的,他们不能告诉JVM“不,保留该内存;我已经完成了这些物品,但我需要空间来存放新物品。通过保留内存,GC不会冒险放弃内存,它将无法恢复。当然,您始终可以获得任何一种方式,但是每次完成对象时都不要不必要地将内存返回给操作系统似乎更合理,因为您已经麻烦地自己获取它。OutOfMemoryException

撇开所有这些不谈,我将尝试直接解决您的一些意见:

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

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

考虑一下如果我在Eclipse中运行内存密集型操作会发生什么;例如,分析。我的内存消耗量会飙升,在此过程中调整堆的大小(可能多次)。完成后,内存要求会下降,但可能不会下降到70%的堆是空闲的。这意味着现在分配了大量未充分利用的空间,JVM 无意释放这些空间。这是一个主要缺点,但您可以通过根据您的情况自定义百分比来解决此问题。为了更好地了解这一点,您确实应该分析您的应用程序,以便您可以看到已利用的堆空间与已分配的堆空间。我个人使用YourKit,但有很多不错的选择可供选择。

*我不知道这是否是唯一的时间,以及如何从操作系统的角度来看观察到这一点,但文档说这是“GC后的最大堆可用百分比以避免收缩”,这似乎暗示了这一点。

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

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

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

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

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


答案 2

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

VM 是二进制文件与系统无关的产物 - 这是一种用于允许字节代码在任何地方执行的解决方案。这类似于模拟器 - 例如对于旧的游戏系统。它正在模仿运行游戏的“机器”。

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

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

与QT等相关的记忆论点是正确的,但并不是故事的全部。虽然它使用的内存比其中一些内存多,但这些内存是针对特定体系结构编译的。自从我使用QT或类似的库以来已经有一段时间了,但我记得内存管理不是很健壮,内存泄漏今天在C / C++程序中仍然很常见。垃圾回收的好处是它删除了许多导致内存泄漏的常见“陷阱”。(注意:并非全部。在Java中仍然很有可能泄漏内存,只是有点困难)。

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