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

我在Linux下运行的Java应用程序有问题。

当我启动应用程序,使用默认的最大堆大小(64mb),我看到使用顶部应用程序,240 MB的虚拟内存分配给应用程序。 这就造成了一些与计算机上的其他软件有关的问题,这是相对资源有限的。

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

有什么办法可以在Linux下configuration虚拟内存用于Java进程吗?

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

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

编辑3:系统中还有其他几个正在运行的应用程序。 系统确实有一个虚拟内存限制。 (来自评论,重要的细节)

这一直是Java的一个长期抱怨,但是这在很大程度上是没有意义的,通常是基于查看错误的信息。 通常的措辞就像“Java上的Hello World需要10兆字节!为什么需要它?” 那么,在64位JVM声明中,您可以通过这种方式使Hello World占用4 GB的空间……至less通过一种度量方式。

 java -Xms1024m -Xmx4096m com.example.Hello

测量记忆的不同方式

在Linux上, 顶部的命令给你几个不同的内存号码。 以下是关于Hello World示例的说明:

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

Windows任务pipe理器的情况有点复杂。 在Windows XP下,有“内存使用”和“虚拟内存大小”列,但官方文档没有提及它们的含义。 Windows Vista和Windows 7添加更多的列,并且它们实际上被logging 。 其中,“工作集”测量是最有用的; 它大致对应于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值在内部用来表示有多less堆在使用中,并在接近该限制时触发垃圾回收。
  • 内存映射的JAR文件,在这种情况下是保存“JDK类”的文件。 在内存映射JAR时,可以非常有效地访问其中的文件(而不是每次从头开始读取)。 Sun JVM将logging映射类path上的所有JAR; 如果您的应用程序代码需要访问JAR,则还可以对其进行内存映射。
  • 两个线程的每线程数据。 1M块是一个线程堆栈; 我不知道什么进入4K块。 对于一个真正的应用程序,你会看到几十个如果没有数百个这些条目重复通过内存映射。
  • 包含实际JVM代码的共享库之一。 有几个这些。
  • C标准库的共享库。 这只是JVM加载不完全是Java的一部分的许多事情之一。

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

虚拟内存大小何时重要?

虚拟内存映射包含了很多东西。 其中一些是只读的,其中一些是共享的,一些是分配的,但从未被触及(例如,在这个例子中,几乎所有的4Gb堆)。 但是操作系统足够智能,只能加载所需的内容,所以虚拟内存大小基本上是不相关的。

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

但是,鉴于64位机器无处不在,我不认为这将是虚拟内存大小是一个完全不相关的统计。

驻地设置大小何时重要?

驻留集大小是实际在RAM中的那部分虚拟内存空间。 如果您的RSS增长成为您的总物理内存的重要部分,那么可能是时候开始担心了。 如果你的RSS增长占用你所有的物理内存,并且你的系统开始交换,那么开始担心已经过去了。

但RSS也是误导,尤其是在轻载的机器上。 操作系统不花费很多精力来回收进程使用的页面。 这样做几乎没有什么好处,并且如果将来该过程涉及页面,则可能导致昂贵的页面错误。 因此,RSS统计信息可能包含大量未被激活的页面。

底线

除非你交换,否则不要太在意各种记忆统计数字告诉你什么。 需要注意的是,不断增长的RSS可能表明存在某种内存泄漏。

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

访问磁盘(即数据库)很贵,而且内存很便宜。 如果你可以交换一个,那么这样做。

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

治疗是设置这个env。 variables: export MALLOC_ARENA_MAX=4如果您正在运行Tomcat,可以将其添加到TOMCAT_HOME/bin/setenv.sh文件中。

有一篇关于设置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

这篇博客文章说

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

在Google或SO上searchMALLOC_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 

分配给Java进程的内存量与我所期望的相当。 在embedded式/内存有限的系统上运行Java时遇到类似的问题。 运行任何具有任意VM限制的应用程序或者在没有足够交换量的系统上运行的应用程序往往会中断。 这似乎是许多现代应用程序的本质,不是devise用于资源有限的系统。

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

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

-XX:MaxPermSize = 64m永久代的大小。 [5.0及更新版本:64位虚拟机的规模扩大了30% 1.4 am64:96m; 1.3.1 – 客户:32米。]

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

你也可以尝试一下,让虚拟机在增加堆的时候不太积极:

-XX:MinHeapFreeRatio = 40为避免扩展,在GC之后的最小堆空闲百分比。

另外,从我几年前的实验中得知,加载的本地库的数量对最小的占用空间有很大的影响。 加载java.net.Socket添加超过15M,如果我记得正确(我可能不)。

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

如果内存是一个问题,考虑使用另一个适合embedded的JVM。 IBM有j9,有开源的“jamvm”,它使用GNU类path库。 此外Sun还在SunSPOTS上运行了Squeak JVM,因此还有其他select。

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

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

减less资源有限的系统堆的一种方法可能是使用-XX:MaxHeapFreeRatiovariables。 这通常设置为70,并且是GC缩小之前自由堆的最大百分比。 将它设置为一个较低的值,你会在例如jvisualvm分析器中看到一个较小的堆栈通常用于你的程序。

编辑:设置-XX的小值:MaxHeapFreeRatio你还必须设置-XX:MinHeapFreeRatio例如

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

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

默认参数

最后,最大堆是905,使用堆是378

MinHeap 10,MaxHeap 25

最后,堆是722,堆是378

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

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

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

  -Xms6291456 -Xms6144k -Xms6m 

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

不,您不能configuration虚拟机所需的内存量。 但是,请注意,这是虚拟内存,不是驻留的,所以如果没有实际使用,它只会停留在那里而不会造成伤害。

换句话说,你可以尝试一些其他的JVM,然后使用更小的内存,但是我不能在这里提供build议。