首页 文章

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

提问于
浏览
229

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

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

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

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

Edit 1 :问题不在于堆 . 问题是,如果我设置一个128 MB的堆,那么Linux仍然会分配210 MB的虚拟内存,这是不需要的 . **

Edit 2 :使用 ulimit -v 允许限制虚拟内存量 . 如果大小设置低于204 MB,则应用程序赢得't run even though it doesn' t需要204 MB,仅64 MB . 所以我想理解为什么Java需要这么多虚拟内存 . 这可以改变吗?

Edit 3 :系统中还运行了其他几个应用程序,这些应用程序是嵌入式的 . 系统确实有虚拟内存限制(来自评论,重要细节) .

8 回答

  • 572

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

    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中的页数 . 在几乎所有情况下,这是你说"too big."时应该使用的唯一数字 . 但它仍然不是一个非常好的数字,特别是在谈论Java时 .

    • SHR是与其他进程共享的驻留内存量 . 对于Java进程,这通常仅限于共享库和内存映射的JAR文件 . 在这个例子中,我只运行了一个Java进程,所以我怀疑7k是操作系统使用的库的结果 .

    • SWAP isn 't turned on by default, and isn' t此处显示 . 它表示当前驻留在磁盘上的虚拟内存量,无论它是否实际位于交换空间中 . 操作系统非常适合将活动页面保存在RAM中,并且交换的唯一方法是(1)购买更多内存,或(2)减少进程数量,因此最好忽略此数字 .

    Windows任务管理器的情况有点复杂 . 在Windows XP下,有"Memory Usage"和"Virtual Memory Size"列,但official documentation对它们的含义保持沉默 . Windows Vista和Windows 7添加了更多列,它们实际上是documented . 其中,"Working Set"测量是最有用的;它大致对应于Linux上RES和SHR的总和 .

    了解虚拟内存映射

    进程消耗的虚拟内存是进程内存映射中所有内容的总和 . 这包括数据(例如,Java堆),但也包括程序使用的所有共享库和内存映射文件 . 在Linux上,您可以使用pmap命令查看映射到进程空间的所有内容(从此处开始我使用的是'm only going to refer to Linux, because it'我是'm sure there are equivalent tools for Windows). Here' s摘自"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 值在内部用于表示程序启动时有多少堆"in use",并在接近该限制时触发垃圾收集 .

    • 内存映射的JAR文件,在本例中是文件保存"JDK classes."当您对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程序,注意堆中发生的事情更为重要 . 消耗的空间总量很重要,您可以采取一些措施来减少这种情况 . 更重要的是您在垃圾收集中花费的时间,以及收集堆的哪些部分 .

    访问磁盘(即数据库)是昂贵的,并且内存便宜 . 如果你可以换一个换另一个,那就这样做吧 .

  • 33

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

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

  • 9

    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

  • 2

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

    另外,你可以尝试一些其他JVM,然后是Sun,内存占用更少,但我不能在这里提出建议 .

  • 0

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

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

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

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

    Default parameters

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

    MinHeap 10, MaxHeap 25

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

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

  • 1

    为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(最大堆大小)设置为尽可能接近应用程序的 actual peak memory usage 的值 . 我相信每次将JVM扩展到最大值时,JVM的默认行为仍然会使堆大小加倍 . 如果你从32M堆开始,你的应用程序达到峰值65M,那么堆将最终增长32M - > 64M - > 128M .

    您也可以尝试这样做以使VM不那么积极地增加堆:

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

    此外,从我几年前的实验中回忆起,加载的本机库数量对最小占用空间产生了巨大影响 . 如果我没记错的话,加载java.net.Socket增加了超过15M(我可能没有) .

  • 7

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

    治愈就是设定这个环境 . 变量: 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文章

    This blog post says

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

    在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
    
  • 2

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

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

相关问题