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


259

我在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:系统中还运行着其他一些嵌入式应用程序。而且系统确实有虚拟内存限制(根据注释和重要细节)。


为什么要担心虚拟内存的使用?如果您真的想担心,请查看常驻内存使用情况,并阅读以下命令:free,ps,top。
basszero

2
系统中还运行着其他一些嵌入式应用程序。而且系统确实有虚拟内存限制。
MarioOrtegón09年


您正在使用哪种Java实现。IIRC是沼泽标准(非OpenJDK)免费的Sun JRE,未获得嵌入式使用许可。
Tom Hawtin-大头钉

我想我错过了“嵌入式”部分的使用……它受内存限制,并且硬件是定制的,但是它仍然是标准计算机
MarioOrtegón09年

Answers:


630

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

java -Xms1024m -Xmx4096m com.example.Hello

测量内存的不同方法

在Linux上,top命令为您提供了几个不同的内存号。关于“ Hello World”示例的内容如下:

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

Windows Task Manager的情况要复杂一些。在Windows XP中,有“内存使用情况”和“虚拟内存大小”列,但是官方文档没有说明它们的含义。Windows Vista和Windows 7添加了更多列,并且它们实际上已在文档中进行了记录。其中,“工作集”度量是最有用的。它大致对应于Linux上RES和SHR的总和。

了解虚拟内存映射

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

0000000040000000 36K rx-/usr/local/java/jdk-1.6-x64/bin/java
0000000040108000 8K rwx-/usr/local/java/jdk-1.6-x64/bin/java
0000000040eba000 676K rwx-- [anon]
00000006fae00000 21248K rwx-- [anon]
00000006fc2c0000 62720K rwx-- [anon]
0000000700000000 699072K rwx-- [anon]
000000072aab0000 2097152K rwx-- [anon]
00000007aaab0000 349504K rwx-- [anon]
00000007c0000000 1048576K rwx-- [anon]
...
00007fa1ed00d000 1652K r-xs- /usr/local/java/jdk-1.6-x64/jre/lib/rt.jar
...
00007fa1ed1d3000 1024K rwx-- [anon]
00007fa1ed2d3000 4K ----- [anon]
00007fa1ed2d4000 1024K rwx-- [anon]
00007fa1ed3d4000 4K ----- [anon]
...
00007fa1f20d3000 164K rx-/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 rx-- /lib/x86_64-linux-gnu/libc-2.13.so
00007fa1f3634000 2044K ----- /lib/x86_64-linux-gnu/libc-2.13.so
00007fa1f3833000 16K rx-/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捕获并转换它到一个StackOverFlowError。对于真实的应用程序,您将在内存映射中看到数十个(如果不是数百个)重复的条目。
  • 共享库之一,其中包含实际的JVM代码。其中有几个。
  • C标准库的共享库。这仅仅是JVM加载的许多事情之一,而Java严格来说并不是。

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

虚拟内存大小何时重要?

虚拟内存映射包含很多东西。其中有些是只读的,有些是共享的,有些是已分配但从未被使用过(例如,本例中几乎所有4Gb的堆)。但是操作系统足够聪明,仅可以加载所需的内容,因此虚拟内存大小基本上无关紧要。

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

但是,鉴于64位计算机无处不在,我认为不久之后虚拟内存大小才是完全不相关的统计数据。

居民集合大小什么时候重要?

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

但是RSS也会引起误解,尤其是在轻载的计算机上。操作系统在回收进程使用的页面上并没有花费很多精力。这样做几乎没有好处,如果将来该过程触及页面,则可能会产生昂贵的页面错误。结果,RSS统计信息可能包含大量未在使用中的页面。

底线

除非您要交换,否则不要过分担心各种内存统计信息会告诉您什么。需要注意的是,不断增长的RSS可能表示某种内存泄漏。

使用Java程序,要注意堆中正在发生的事情,这一点更为重要。消耗的空间总量很重要,您可以采取一些步骤来减少空间消耗。更重要的是您花费在垃圾收集上的时间,以及要收集堆的哪些部分。

访问磁盘(即数据库)非常昂贵,而内存则很便宜。如果您可以互相交易,那就这样做。


9
您应考虑到RES度量缺少当前换出的内存部分。因此,您可能具有较低的RES值,但这仅是因为该应用程序处于非活动状态并且大部分堆都换出到了磁盘上。Java在交换方面做得很糟糕:在每个完整的GC上,大部分堆都会被遍历和复制,因此,如果交换中有很多堆,则GC必须将其全部加载回主内存。
jrudolph 2010年

1
很好的答案kdgregory!我正在使用没有交换空间的CF在嵌入式环境中运行。因此,根据您的回答,我所有的VIRT,SWAP和nFLT值都来自内存映射文件...这对我来说很有意义。您是否知道SWAP值表示尚未加载到内存中的页面还是已换出内存的页面,或者两者都表示?我们如何了解可能发生的颠簸(连续映射,然后换出)?
2010年

2
@Jeach-我惊讶地发现有任何交换,因此启动了我的“ traveling Linux”(Ubuntu 10.04的拇指驱动器,没有交换)。当启用顶部的“ SWAP”列时,我看到Eclipse的长度为509m。然后,我用pmap对其进行查看时,总虚拟空间为650m。因此,我怀疑“ SWAP”图代表了所有磁盘上的页面,而不仅仅是那些不在内存中的页面。
kdgregory

2
关于第二个问题:如果您不断从闪存卡中读取页面,则您的IO等待时间(在顶部的摘要中显示为“%wa”)应该很高。但是要注意,对于任何活动,尤其是写操作,这都非常重要(假设您的程序执行任何操作)。
kdgregory

1
> 1M块是线程堆栈;我不知道4K块中的内容。4K块(被标记为既没有读取权限也没有写权限)可能是保护块。堆栈溢出时,将访问此区域,从而触发故障,然后JVM可以通过生成Java StackOverflowException处理该故障。这比在每次方法调用时检查堆栈指针便宜。在其他上下文中也可以看到未设置权限的保护区域。
eriksoe

38

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

这篇博客文章说

已知常驻内存以类似于内存泄漏或内存碎片的方式蠕变。

还有一个开放的JDK错误JDK-8193521“ glibc使用默认配置浪费了内存”

在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

这个答案确实在带TomEE服务器的64位Ubuntu服务器上给了我很大的帮助,“ TomEE消耗了内存”。与IBM文章的链接确实是一个深刻的解释。再次感谢您提供这个好提示!
MWiesner,2015年

1
JVM可能会泄漏本机内存,从而导致类似的症状。请参阅stackoverflow.com/a/35610063/166062。未关闭的GZIPInputStream和GZIPOutputStream实例也可能是泄漏的来源。
Lari Hotari '16

3
Java 8中有一个JVM Bug,它导致无限制的本机内存增长:bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK - 8164293-如果这对您有影响,使用MALLOC_ARENA_MAX可能会减慢您的内存增长,但不会彻底解决问题。
outofcoffee

@LariHotari非常感谢你的努力,您指出的glibc和RedHat版本
萨姆

2
Java 8u131包含相关JVM错误JDK-8164293 bugs.openjdk.java.net/browse/JDK-8178124的反向移植错误 修复
拉里·霍塔里

9

为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以上(而且我可能没有)。


7

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

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


是否可以禁用热点?
MarioOrtegón09年

也许。检查您使用的JVM的命令行选项。
托尔比约恩Ravn的安徒生

3

只是一个想法,但你可以检查的影响一个ulimit -v选项

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


那正是我的问题。我的堆设置为64M,但Linux保留204MB。如果我将ulimit设置为204以下,则该应用程序根本不会运行。
MarioOrtegón09年

有趣的是:设置ulimit可能会对其他进程产生意想不到的副作用,从而说明了应用程序无法运行的原因。
VonC

问题似乎是Java要求保留更大的虚拟内存,即使它不会使用它也是如此。在Windows中,使用的虚拟内存和Xmx设置非常接近。
MarioOrtegón09年

您是否使用JRockit JVM进行了尝试?
VonC

由于JVM的内存分配是堆分配和Perm大小的总和(可以使用-Xms和-Xmx选项固定第一个),因此是否尝试使用-XX:PermSize和-XX:MaxPermSize进行某些设置(默认值从32MB到64MB,具体取决于JVM版本)?
VonC

3

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

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

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

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

默认参数

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

MinHeap 10,MaxHeap 25

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

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


1

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

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

           -Xms6291456
           -Xms6144k
           -Xms6m

-Xmxn指定内存分配池的最大大小(以字节为单位)。此值必须是大于2 MB的1024的倍数。追加字母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


1
我的问题不是堆大小,而是Linux分配的虚拟内存量
MarioOrtegón09年

阅读kdgregory的说明。减少堆大小,“新大小”和其他可配置参数将减少jvm占用的REAL内存量。
Paul Tomblin,2009年

他可能有合理的问题。某些应用程序(如我写的应用程序)映射一个1 GB的文件,而某些系统只有2 GB的虚拟内存,其中一些被共享库填充。如果这是问题所在,他肯定应该禁用DSO随机化。/ proc中有一个选项。
Zan Lynx 2010年

0

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

替代地,您可以尝试使用其他的JVM,然后再尝试使用Sun的其他JVM,但其内存占用空间较小,但是我在这里不建议这样做。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.