Linux下Java的虚拟内存使用情况,使用的内存过多

Posted

技术标签:

【中文标题】Linux下Java的虚拟内存使用情况,使用的内存过多【英文标题】:Virtual Memory Usage from Java under Linux, too much memory used 【发布时间】:2010-10-08 08:32:37 【问题描述】:

我遇到了在 Linux 下运行的 Java 应用程序的问题。

当我使用默认的最大堆大小 (64 MB) 启动应用程序时,我看到使用 tops 应用程序为应用程序分配了 240 MB 的虚拟内存。这会给计算机上的一些其他软件带来一些问题,这些软件资源相对有限。

据我所知,保留的虚拟内存无论如何都不会被使用,因为一旦达到堆限制,就会抛出OutOfMemoryError。我在 windows 下运行相同的应用程序,我发现虚拟内存大小和堆大小相似。

我是否可以在 Linux 下为 Java 进程配置正在使用的虚拟内存?

编辑 1:问题不在于堆。问题是,如果我设置一个 128 MB 的堆,例如,Linux 仍然分配 210 MB 的虚拟内存,这永远不需要。**

编辑 2:使用ulimit -v 可以限制虚拟内存的数量。如果设置的大小低于 204 MB,则应用程序将不会运行,即使它不需要 204 MB,只需要 64 MB。所以我想了解为什么 Java 需要这么多虚拟内存。这个可以改吗?

编辑 3:系统中运行着其他几个应用程序,这些应用程序是嵌入式的。而且系统确实有虚拟内存限制(来自 cmets,重要细节)。

【问题讨论】:

为什么要关心虚拟内存的使用?如果您真的想担心,请查看常驻内存使用情况并阅读以下命令:free、ps、top。 系统中运行着其他几个应用程序,这些应用程序是嵌入式的。而且系统确实有虚拟内存限制。 啊哈,魔鬼在细节中 您使用的是哪种 Java 实现。 IIRC,沼泽标准(非 OpenJDK)免费 Sun JRE 未获得嵌入式使用许可。 我想我错过了“嵌入式”部分......它是内存有限的,硬件是定制的,但它仍然是一台标准计算机 【参考方案1】:

这是对 Java 的长期抱怨,但它在很大程度上毫无意义,而且通常是基于查看错误的信息。通常的措辞类似于“Java 上的 Hello World 需要 10 兆字节!为什么需要它?”好吧,这是一种让 64 位 JVM 上的 Hello World 声称占用 4 GB 的方法……至少通过一种测量形式。

java -Xms1024m -Xmx4096m com.example.Hello

测量内存的不同方法

在 Linux 上,top 命令为您提供了几个不同的内存数字。以下是关于 Hello World 示例的说明:

PID 用户 PR NI VIRT RES SHR S %CPU %MEM TIME+ 命令 2120 kgregory 20 0 4373m 15m 7152 S 0 0.2 0:00.10 爪哇 VIRT 是虚拟内存空间:虚拟内存映射中所有内容的总和(见下文)。它在很大程度上是没有意义的,除非它不是(见下文)。 RES 是驻留集大小:当前驻留在 RAM 中的页数。在几乎所有情况下,这是您在说“太大”时应该使用的唯一数字。但这仍然不是一个很好的数字,尤其是在谈到 Java 时。 SHR 是与其他进程共享的常驻内存量。对于 Java 进程,这通常仅限于共享库和内存映射 JAR 文件。在这个例子中,我只运行了一个 Java 进程,所以我怀疑 7k 是操作系统使用的库的结果。 默认情况下,SWAP 未打开,因此此处未显示。它表示当前驻留在磁盘上的虚拟内存量,它实际上是否在交换空间中。操作系统非常擅长将活动页面保留在 RAM 中,而交换的唯一解决方法是 (1) 购买更多内存,或 (2) 减少进程数量,因此最好忽略这个数字。

Windows 任务管理器的情况有点复杂。在 Windows XP 下,有“内存使用”和“虚拟内存大小”列,但 official documentation 没有说明它们的含义。 Windows Vista 和 Windows 7 增加了更多的列,它们实际上是documented。其中,“工作集”测量是最有用的;它大致对应于Linux上的RES和SHR之和。

了解虚拟内存映射

进程消耗的虚拟内存是进程内存映射中所有内容的总和。这包括数据(例如,Java 堆),还包括程序使用的所有共享库和内存映射文件。在 Linux 上,您可以使用 pmap 命令查看映射到进程空间的所有内容(从这里开始,我将仅提及 Linux,因为它是我使用的;我确信有等价的Windows 工具)。这是“Hello World”程序的内存映射的摘录;整个内存映射超过 100 行,拥有一千行的列表并不罕见。

0000000040000000 36K r-x-- /usr/local/java/jdk-1.6-x64/bin/java 0000000040108000 8K rwx-- /usr/local/java/jdk-1.6-x64/bin/java 0000000040eba000 676K rwx-- [匿名] 00000006fae00000 21248K rwx-- [匿名] 00000006fc2c0000 62720K rwx-- [匿名] 0000000700000000 699072K rwx-- [匿名] 000000072aab0000 2097152K rwx-- [匿名] 00000007aaab0000 349504K rwx-- [匿名] 00000007c0000000 1048576K rwx-- [匿名] ... 00007fa1ed00d000 1652K r-xs-/usr/local/java/jdk-1.6-x64/jre/lib/rt.jar ... 00007fa1ed1d3000 1024K rwx-- [匿名] 00007fa1ed2d3000 4K ----- [匿名] 00007fa1ed2d4000 1024K rwx-- [匿名] 00007fa1ed3d4000 4K ----- [匿名] ... 00007fa1f20d3000 164K r-x-- /usr/local/java/jdk-1.6-x64/jre/lib/amd64/libjava.so 00007fa1f20fc000 1020K ----- /usr/local/java/jdk-1.6-x64/jre/lib/amd64/libjava.so 00007fa1f21fb000 28K rwx--/usr/local/java/jdk-1.6-x64/jre/lib/amd64/libjava.so ... 00007fa1f34aa000 1576K r-x-- /lib/x86_64-linux-gnu/libc-2.13.so 00007fa1f3634000 2044K ----- /lib/x86_64-linux-gnu/libc-2.13.so 00007fa1f3833000 16K r-x-- /lib/x86_64-linux-gnu/libc-2.13.so 00007fa1f3837000 4K rwx-- /lib/x86_64-linux-gnu/libc-2.13.so ...

格式的快速解释:每一行都以段的虚拟内存地址开始。接下来是段大小、权限和段的来源。最后一项是文件或“anon”,表示通过mmap分配的内存块。

从顶部开始,我们有

JVM 加载程序(即,当您键入java 时运行的程序)。这是非常小的;它所做的只是加载到存储真实 JVM 代码的共享库中。 一堆保存 Java 堆和内部数据的匿名块。这是一个 Sun JVM,所以堆被分成多代,每一代都是它自己的内存块。注意JVM根据-Xmx的值分配虚拟内存空间;这允许它有一个连续的堆。 -Xms 值在内部用于说明程序启动时有多少堆“正在使用”,并在接近该限制时触发垃圾回收。 内存映射的 JAR 文件,在本例中是保存“JDK 类”的文件。当您对 JAR 进行内存映射时,您可以非常有效地访问其中的文件(而不是每次从头开始读取)。 Sun JVM 将对类路径上的所有 JAR 进行内存映射;如果您的应用程序代码需要访问 JAR,您还可以对其进行内存映射。 两个线程的每线程数据。 1M 块是线程栈。我对 4k 块没有很好的解释,但@ericsoe 将其识别为“保护块”:它没有读/写权限,因此如果访问会导致段错误,JVM 会捕获并翻译发送到***Error。对于一个真正的应用程序,您会看到数十个甚至数百个这样的条目在内存映射中重复出现。 保存实际 JVM 代码的共享库之一。其中有好几个。 C 标准库的共享库。这只是 JVM 加载的许多不属于 Java 的内容之一。

共享库特别有趣:每个共享库至少有两个段:一个包含库代码的只读段,一个包含库的全局每个进程数据的读写段(我不知道没有权限的段是什么;我只在 x64 Linux 上看到过)。库的只读部分可以在所有使用该库的进程之间共享;例如,libc 有 1.5M 的虚拟内存空间可以共享。

什么时候虚拟内存大小很重要?

虚拟内存映射包含很多东西。其中一些是只读的,一些是共享的,还有一些是已分配但从未接触过的(例如,本例中几乎所有的 4Gb 堆)。但是操作系统足够智能,只能加载它需要的内容,因此虚拟内存大小在很大程度上是无关紧要的。

如果您在 32 位操作系统上运行,则虚拟内存大小很重要,您只能分配 2Gb(或在某些情况下为 3Gb)的进程地址空间。在这种情况下,您正在处理稀缺资源,并且可能必须做出权衡,例如减小堆大小以便内存映射大文件或创建大量线程。

但是,鉴于 64 位机器无处不在,我认为用不了多久虚拟内存大小就会成为一个完全不相关的统计数据。

驻留集大小何时重要?

驻留集大小是实际在 RAM 中的那部分虚拟内存空间。如果您的 RSS 增长到您的总物理内存的很大一部分,那么可能是时候开始担心了。如果您的 RSS 增长到占用您所有的物理内存,并且您的系统开始交换,那么现在就该开始担心了。

但是 RSS 也具有误导性,尤其是在负载较轻的机器上。操作系统不会花费很多精力来回收进程使用的页面。这样做几乎没有什么好处,而且如果进程在未来接触到页面,则可能会出现代价高昂的页面错误。因此,RSS 统计信息可能包含大量未使用的页面。

底线

除非您要进行交换,否则不要过分关注各种内存统计信息告诉您的内容。需要注意的是,不断增长的 RSS 可能表明存在某种内存泄漏。

对于 Java 程序,关注堆中发生的事情要重要得多。占用的空间总量很重要,您可以采取一些步骤来减少它。更重要的是您在垃圾收集上花费的时间,以及堆的哪些部分正在被收集。

访问磁盘(即数据库)很昂贵,而内存很便宜。如果你可以用一个换另一个,那就去做吧。

【讨论】:

您应该考虑到 RES 度量中缺少当前换出的内存部分。因此,您的 RES 值可能较低,但这仅仅是因为应用程序处于非活动状态并且大部分堆已换出到磁盘。 Java 在交换方面做得很糟糕:在每次完整的 GC 上,大部分堆都被遍历和复制,所以如果你的大部分堆处于交换状态,GC 必须将它们全部加载回主内存。 很好的答案 kdgregory!我正在使用没有交换空间的 CF 在嵌入式环境中运行。因此,根据您的回答,我所有的 VIRT、SWAP 和 nFLT 值都来自内存映射文件……这对喵喵来说很有意义。您知道 SWAP 值是表示尚未加载到内存中的页面还是已换出内存的页面,还是两者兼而有之?我们如何才能了解可能的颠簸(连续映射然后换出)? @Jeach - 我很惊讶报告了任何交换,因此启动了我的“旅行 Linux”(一个带有 Ubuntu 10.04 且没有交换的拇指驱动器)。当我在 top 中启用“SWAP”列时,我看到 Eclipse 有 509m。然后我用pmap查看,总的虚拟空间是650m。所以我怀疑“SWAP”数字代表所有磁盘页面,而不仅仅是那些不在内存中的页面。 关于你的第二个问题:如果你经常从闪存卡读取页面,你的IO等待时间(在top的摘要中显示为“%wa”)应该很高。但是请注意,对于任何活动,尤其是写入(假设您的程序执行任何活动),这都会很高。 > 1M块是线程栈;我不知道 4K 块中有什么。 4K 块 - 被标记为既没有读取权限也没有写入权限 - 可能是一个保护块。在堆栈溢出时,会访问该区域,这会触发故障,然后 JVM 可以通过生成 Java ***Exception 来处理该故障。这比在每个方法调用时检查堆栈指针要便宜得多。未设置权限的警戒区域也可以在其他上下文中使用。【参考方案2】:

Java 和 glibc >= 2.10(包括 Ubuntu >= 10.04,RHEL >= 6)存在一个已知问题。

解决方法是设置这个环境。变量:

export MALLOC_ARENA_MAX=4

如果您正在运行 Tomcat,您可以将其添加到 TOMCAT_HOME/bin/setenv.sh 文件中。

对于 Docker,将其添加到 Dockerfile

ENV MALLOC_ARENA_MAX=4

有一篇关于设置 MALLOC_ARENA_MAX 的 IBM 文章 https://www.ibm.com/developerworks/community/blogs/kevgrig/entry/linux_glibc_2_10_rhel_6_malloc_may_show_excessive_virtual_memory_usage?lang=en

This blog post says

已知常驻内存会以类似于 内存泄漏或内存碎片。

还有一个开放的JDK错误JDK-8193521 "glibc wastes memory with default configuration"

在 Google 或 SO 上搜索 MALLOC_ARENA_MAX 以获取更多参考。

您可能还需要调整其他 malloc 选项以优化分配内存的低碎片:

# tune glibc memory allocation, optimize for low fragmentation
# limit the number of arenas
export MALLOC_ARENA_MAX=2
# disable dynamic mmap threshold, see M_MMAP_THRESHOLD in "man mallopt"
export MALLOC_MMAP_THRESHOLD_=131072
export MALLOC_TRIM_THRESHOLD_=131072
export MALLOC_TOP_PAD_=131072
export MALLOC_MMAP_MAX_=65536

【讨论】:

这个答案确实帮助了我在 64 位 Ubuntu 服务器上使用 TomEE 服务器,该服务器有点“消耗内存”。 IBM 文章的链接确实是一个深刻的解释。再次感谢这个好提示! JVM 可能会泄漏本机内存,从而导致类似的症状。见***.com/a/35610063/166062。未关闭的 GZIPInputStream 和 GZIPOutputStream 实例也可能是泄漏源。 Java 8 中有一个 JVM 错误,导致本机内存无限增长:bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8164293 - 如果这对您有影响,使用MALLOC_ARENA_MAX 可能会减慢您的内存增长,但不能解决问题完全。 @LariHotari 非常感谢您指出 glibc 和 redhat 版本的努力 Java 8u131 包含针对相关 JVM 错误 JDK-8164293 bugs.openjdk.java.net/browse/JDK-8178124 的反向移植错误修复。【参考方案3】:

为 Java 进程分配的内存量与我的预期相当。我在嵌入式/内存有限的系统上运行 Java 时遇到了类似的问题。使用任意 VM 限制或在没有足够交换量的系统上运行 任何 应用程序往往会中断。这似乎是许多现代应用程序的本质,它们不是为在资源有限的系统上使用而设计的。

您可以尝试使用更多选项来限制 JVM 的内存占用。这可能会减少虚拟内存占用:

-XX:ReservedCodeCacheSize=32m 保留代码缓存大小(以字节为单位)- 最大值 代码缓存大小。 [Solaris 64 位, amd64,和-server x86:48m;在 1.5.0_06 及更早版本,Solaris 64 位和 and64:1024m。]

-XX:MaxPermSize=64m 永久代的大小。 [5.0 及更高版本: 64 位 VM 的规模扩大了 30%; 1.4 amd64:96m; 1.3.1-客户端:32m。]

此外,您还应该将 -Xmx(最大堆大小)设置为尽可能接近应用程序的实际峰值内存使用量的值。我相信 JVM 的默认行为仍然是每次将堆大小扩展到最大值时 加倍。如果您从 32M 堆开始,而您的应用达到 65M 的峰值,那么堆最终将增长 32M -> 64M -> 128M。

您也可以尝试这样做,以降低 VM 在堆增长方面的积极性:

-XX:MinHeapFreeRatio=40 GC 后堆空闲的最小百分比 避免膨胀。

此外,根据我几年前的试验记忆,加载的本机库的数量对最小占用空间有很大影响。如果我没记错的话,加载 java.net.Socket 增加了超过 15M(我可能没有)。

【讨论】:

【参考方案4】:

Sun JVM 需要大量内存用于 HotSpot,它映射到共享内存中的运行时库。

如果内存是个问题,请考虑使用另一个适合嵌入的 JVM。 IBM 有 j9,还有使用 GNU 类路径库的开源“jamvm”。此外,Sun 在 SunSPOTS 上运行 Squeak JVM,因此还有其他选择。

【讨论】:

是否可以选择禁用热点? 也许吧。检查您使用的 JVM 的命令行选项。【参考方案5】:

减少资源有限的系统的堆容量的一种方法可能是使用 -XX:MaxHeapFreeRatio 变量。这通常设置为 70,并且是在 GC 收缩之前可用的堆的最大百分比。将其设置为较低的值,您将在例如 jvisualvm 分析器中看到较小的堆 sice 通常用于您的程序。

编辑:要为 -XX:MaxHeapFreeRatio 设置较小的值,您还必须设置 -XX:MinHeapFreeRatio 比如

java -XX:MinHeapFreeRatio=10 -XX:MaxHeapFreeRatio=25 HelloWorld

EDIT2:为启动并执行相同任务的实际应用程序添加了一个示例,一个使用默认参数,一个使用 10 和 25 作为参数。我没有注意到任何真正的速度差异,尽管理论上 java 在后一个示例中应该使用更多时间来增加堆。

最后,最大堆为 905,已用堆为 378

最后,最大堆为 722,已用堆为 378

这实际上有一些影响,因为我们的应用程序运行在远程桌面服务器上,许多用户可能同时运行它。

【讨论】:

【参考方案6】:

只是一个想法,但您可以检查a ulimit -v option的影响。

这不是一个实际的解决方案,因为它会限制可用于所有进程的地址空间,但这将允许您使用有限的虚拟内存检查应用程序的行为。

【讨论】:

这正是我的问题所在。我的堆设置为 64M,但 linux 保留 204MB。如果我将 ulimit 设置为低于 204,则应用程序根本不会运行。 有趣:设置 ulimit 可能会对其他进程产生意想不到的副作用,解释为什么应用程序无法运行。 问题似乎是 Java 需要保留这么多的虚拟内存,即使它不会使用它。在 Windows 中,使用的虚拟内存和 Xmx 设置更接近。 你用 JRockit JVM 试过了吗? 由于 JVM 的内存分配是堆分配和永久大小的总和(第一个可以使用 -Xms 和 -Xmx 选项修复),您是否尝试了一些设置 - XX:PermSize 和 -XX:MaxPermSize(默认从 32MB 到 64MB,取决于 JVM 版本)?【参考方案7】:

Sun 的 java 1.4 有以下参数来控制内存大小:

-Xmsn 指定内存分配池的初始大小(以字节为单位)。 该值必须是 1024 的倍数 大于 1MB。附加字母 k 或 K 表示千字节,或 m 或 M 表示兆字节。默认 值为 2MB。例子:

           -Xms6291456
           -Xms6144k
           -Xms6m

-Xmxn 指定内存分配池的最大大小(以字节为单位)。 该值必须是 1024 的倍数 大于 2MB。附加字母 k 或 K 表示千字节,或 m 或 M 表示兆字节。默认 值为 64MB。例子:

           -Xmx83886080
           -Xmx81920k
           -Xmx80m

http://java.sun.com/j2se/1.4.2/docs/tooldocs/windows/java.html

Java 5 和 6 有更多。见http://java.sun.com/javase/technologies/hotspot/vmoptions.jsp

【讨论】:

我的问题不在于堆大小,而在于 Linux 分配的虚拟内存量 阅读 kdgregory 的解释。减少堆大小、“新大小”和其他可配置参数将减少 jvm 占用的 REAL 内存量。 他可能有合法的问题。一些应用程序(比如我写的一个)映射一个 1 GB 的文件,而一些系统只有 2 GB 的虚拟内存,其中一些被共享库填充。如果这是问题,他绝对应该禁用 DSO 随机化。 /proc中有一个选项。【参考方案8】:

不,您无法配置 VM 所需的内存量。但是请注意,这是虚拟内存,而不是常驻内存,因此如果不实际使用,它只会留在那里而不会造成伤害。

或者,您可以尝试其他一些 JVM,然后是 Sun 的,内存占用更小,但我在这里不建议。

【讨论】:

以上是关于Linux下Java的虚拟内存使用情况,使用的内存过多的主要内容,如果未能解决你的问题,请参考以下文章

java能远程监控linux主机的cpu和内存使用情况么

linux查看内存使用情况

如何查看 进程内存使用情况 linux

Linux下内存空间分配物理地址与虚拟地址映射

Linux下内存空间分配物理地址与虚拟地址映射

Linux下内存空间分配物理地址与虚拟地址映射