我在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:系统中运行了几个其他应用程序,它们是嵌入式的。系统确实有虚拟内存限制(来自评论,重要细节)。
这一直是Java的长期抱怨,但它在很大程度上毫无意义,通常基于查看错误的信息。通常的措辞就像“Hello World on Java需要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
Windows任务管理器的情况有点复杂。在Windows XP下,有“内存使用”和“虚拟内存大小”列,但official documentation对它们的含义保持沉默。 Windows Vista和Windows 7添加了更多列,它们实际上是documented。其中,“工作集”测量是最有用的;它大致对应于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分配的内存块。
从顶部开始,我们有
java
时运行的程序)。这个很小;它所做的就是加载存储真实JVM代码的共享库。-Xmx
值分配虚拟内存空间;这允许它有一个连续的堆。 -Xms
值在内部用于表示程序启动时有多少堆“正在使用”,并在接近该限制时触发垃圾收集。共享库特别有趣:每个共享库至少有两个段:一个包含库代码的只读段,以及一个包含库的全局每个进程数据的读写段(我不知道是什么没有权限的段是;我只在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)。
治愈就是设定这个环境。变量:
export MALLOC_ARENA_MAX=4
如果您正在运行Tomcat,则可以将其添加到TOMCAT_HOME/bin/setenv.sh
文件中。
对于Docker,将其添加到Dockerfile
ENV MALLOC_ARENA_MAX=4
有一篇关于设置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文章
已知驻留内存以类似于内存泄漏或内存碎片的方式蠕变。
还有一个开放的JDK bug 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
为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 -client:32m。]
此外,还应将-Xmx(最大堆大小)设置为尽可能接近应用程序的实际峰值内存使用量的值。我相信每次将JVM扩展到最大值时,JVM的默认行为仍然是堆大小的两倍。如果你从32M堆开始,你的应用程序达到峰值65M,那么堆将最终增长32M - > 64M - > 128M。
您也可以尝试这样做以使VM不那么积极地增加堆:
-XX:MinHeapFreeRatio = 40 GC之后的最小堆空闲百分比以避免扩展。
此外,从我几年前从实验中回忆起的情况来看,加载的本地库的数量对最小占用空间产生了巨大影响。如果我没记错的话,加载java.net.Socket增加了超过15M(我可能没有)。
Sun JVM需要大量内存用于HotSpot,并且它映射到共享内存中的运行时库中。
如果内存是一个问题,请考虑使用另一个适合嵌入的JVM。 IBM有j9,还有开源“jamvm”,它使用GNU类路径库。 Sun还在SunSPOTS上运行Squeak JVM,因此有其他选择。
只是一个想法,但你可以检查a ulimit -v
option的影响。
这不是一个实际的解决方案,因为它会限制所有进程可用的地址空间,但这将允许您使用有限的虚拟内存检查应用程序的行为。
减少具有有限资源的系统的堆栈的一种方法可能是使用-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
这实际上有一些影响,因为我们的应用程序在远程桌面服务器上运行,许多用户可以立即运行它。
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
不,您无法配置VM所需的内存量。但请注意,这是虚拟内存,而不是驻留,因此如果没有实际使用,它只会保持在那里而不会造成伤害。
另外,你可以尝试一些其他JVM,然后是Sun,内存占用更少,但我不能在这里提出建议。