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

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

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

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

无论如何,我可以配置在Linux下用于Java进程的虚拟内存?

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

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

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


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

java -Xms1024m -Xmx4096m com.example.Hello

测量记忆的不同方法

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

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
 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默认情况下未打开,并且未在此处显示。 它指示当前驻留在磁盘上的虚拟内存量,无论它是否实际位于交换空间中。 操作系统非常适合将活动页面保存在RAM中,交换的唯一方法是(1)购买更多的内存,或(2)减少进程数量,因此最好忽略这个数字。
  • Windows任务管理器的情况稍微复杂一点。 在Windows XP下,有“内存使用”和“虚拟内存大小”列,但官方文档没有提及它们的含义。 Windows Vista和Windows 7添加更多列,并且它们实际上已记录在案。 其中,“工作集”测量是最有用的; 它大致对应于Linux上RES和SHR的总和。

    了解虚拟内存映射

    进程占用的虚拟内存是进程内存映射中所有内容的总和。 这包括数据(例如Java堆),还包括程序使用的所有共享库和内存映射文件。 在Linux上,您可以使用pmap命令查看映射到进程空间的所有内容(从这里开始,我只会引用Linux,因为这是我使用的;我确信有相当的工具可用于视窗)。 下面是“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--    [ 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 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块。 对于一个真正的应用程序,如果没有数百个这样的条目在内存映射中重复,您将看到数十个条目。
  • 包含实际JVM代码的共享库之一。 有几个这样的。
  • C标准库的共享库。 这只是JVM加载的许多事情之一,并非严格意义上的Java部分。
  • 共享库特别有趣:每个共享库至少有两个段:包含库代码的只读段,以及包含库的全局每进程数据的读写段(我不知道没有权限的段是;我只在x64 Linux上看过它)。 库的只读部分可以在使用库的所有进程之间共享; 例如, libc拥有1.5M的可共享的虚拟内存空间。

    虚拟内存大小何时重要?

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

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

    但是,考虑到64位机器无处不在,我认为在虚拟内存大小是完全不相关的统计数据之前不会太久。

    驻地组大小何时重要?

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

    但RSS也是误导性的,特别是在轻载机器上。 操作系统不花费很多精力来回收进程使用的页面。 这样做并没有什么好处,并且如果流程在将来触及页面,可能会导致出现昂贵的页面错误。 因此,RSS统计信息可能包含很多未被激活使用的页面。

    底线

    除非你交换,否则不要过分关注各种内存统计信息告诉你的内容。 随着不断增长的RSS可能指示某种内存泄漏的警告。

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

    访问磁盘(即数据库)价格昂贵,内存便宜。 如果您可以交换另一个,请这样做。


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

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

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

    这篇博客文章说

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

    在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
    

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

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

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

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

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

    您也可以尝试使虚拟机对于增长堆不那么积极:

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

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

    链接地址: http://www.djcxy.com/p/14555.html

    上一篇: Virtual Memory Usage from Java under Linux, too much memory used

    下一篇: Java maximum memory on Windows XP