https://stackoverflow.com/questions/561245/virtual-memory-usage-from-java-under-linux-too-much-memory-used
提问:
我有个关于Java应用在Linux下运行的问题
当我启动一个程序,使用默认的最大堆内存(64MB),我通过top程序看到给这个程序分配了240MB虚拟内存。这给计算机上的其他程序带来了问题,这可能是资源有限。
我理解的是,这些预订的内存并没有被使用,因为有一次我们遇到了OutOfMemery错误。我在windows下跑了这个程序,我发现虚拟内存大小和它的堆内存是一样大的。
有办法可以配置Linux下Java进程的虚拟内存大小吗?
第一次编辑:这个问题不是堆的原因。问题是当我设置堆内存为128MB时,Linux仍然分配了210MB内存,并不需要这么多内存
第二次编辑:使用命令 ulimite -v
来限制虚拟内存的大小,如果把这个大小小于204MB,则这个程序就运行不起来了,即使他不需要204MB。只需要64MB。所以我想知道,Java为何需要这么多虚拟内存,这个可以更改吗?
第三次编辑:系统上还跑着其他几个程序,是嵌入式的(?),并且系统没有虚拟内存限制
标签 java linux memory virtual-memory
回答1:589赞
Java的这个问题已经的抱怨已久,但是大多时候没意义,并且同时需要基于错误信息。比较常见的一个说法比如:Java 上的HelloWorld程序需要10MB,它为何需要(那么多内存)?好吧,这里有个办法可以让一个在64位 JVM的 hello-world程序使用4GB内存,至少通过一种测量形式展现:
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的任务管理器中的内存,这更麻烦点儿。XP下有“内存使用”和“虚拟聂村大小”两栏,但是官方文档没解释它们代表什么含义。Vista和Win7加了更多的栏,有文档。在这其中,“Working Set”工作集测量最有意义,大约相当于Linux下的 RES加SHR。
程序消耗的虚拟内存是程序的所有内存映射的和。这包含了数据(比如Java的堆),和所有的共享库进和进程使用的内存映射文件。在Linux下,你可以使用pmap
命令去看进程空间所映射的所有东西(在这里我说的是Linux,我确定Windows下也有相同的工具),下面这个示例是Hello World程序的内存映射,完整的内存映射超过了100行,就是1000行也不奇怪。
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
…
明天见!~
我回来了!
吭吭,继续
简单解释一下上面的格式:每行的开始是虚拟内存段的起始地址,接下来是段大小,权限,段的来源(?)。最后一栏要么是个文件,要么是“anno”,anno
表示这块内存是mmap系统调用*[map or unmap files or devices into memory ]*分配的。
我们从上面的实例,得到了:
JVM装载器(就是你键入Java而运行的程序)。这个非常小;它只做一件事:加载共享库,真正的JVM 代码就在那里
一串 anno块,那些都是由Java的堆和内部数据持有。这是一个Sun JVM,所以堆被分成了几代,每个有各自的内存块。注意到JVM是基于参数-Xms
分配虚拟内存的,这允许它拥有一片连续的堆内存。-Xms
的值是指当程序启动时使用了多大的堆内存,并且当达到限制时会触发垃圾回收。
一个内存映射的JAR文件[内存映射点击],在这个例子中这个文件持有“JDK classes”。当你内存映射一个JAR时,你可以通过内存映射高效地获取这个文件。Sun JVM会
将所有classpath中的JAR文件做内存映射,如果你的应用程序需要获取一个JAR,你也可以使用内存映射。(内存映射,使用mmap系统调用将一个文件/一块内存(用来共享内存)由内核空间映射到程序空间)
一个线程有两个线程数据?每个线程数据用于两个线程?1MB的块是线程栈,我不知道另一个4KB是做什么用的。对于一个真正的应用程序,你看不见几百个也会看见几十个这样的内存映射重复出现。
-这些共享库中 其中一个共享库持有真正的JVM代码,这里的共享库中有好几个这种的。
C标准库的共享库。这些是JVM加载的,严格来说不属于Java。
这些共享库很有趣:每个共享库至少有两个段,一个只读段包含代码,还有一个可读写段包含了共享库在全局每个进程数据(我不知道这些没有权限的段是干嘛的,只在64位Linux上看到过)。库的只读部分可以在所有使用了这个库进程间共享;比如,libc的1.5MB虚拟内存可以共享。
虚拟内存包含了许多东西。一些是只读的,一些是共享的,还有一些分配了从来没用过(比如这个例子中4GB堆内存的绝大部分)。但是操作系统足够智能到加载进程所需要的部分,所以,虚拟内存大小大多时候是无关的。
当你运行在32位操作系统时,虚拟内存的大小就显得重要了,在32位操作系统只能分配2GB,或者有时的3GB的进程地址空间*(我没记错的话64位也只能分配2^46=64TB)*。那种情况下,你的资源紧缺,所以就要有所取舍,比如减少堆内存去内存映射大文件或者创建大量线程。
但是,鉴于64位操作系统很普遍了,我认为过不了多久就考虑到对虚拟内存大小的统计。
常驻内存集大小是虚拟内存的一部分,在RAM中的那部分。如果你的常驻内存集增长到栈你的物理内存一定比例时,你要开始担心了。如果常驻内存集用完了你的物理内存,操作系统就要开始交换内存了,此时已经不是担心的时候了(是哭的时候?)。
但是RSS也会有误导,特别是负载较轻的机器。重新声明一个进程的页也不是很费劲。这样的做没啥好处,同时也有潜在的问题:在以后读取到该页导致的页错误。结果就是,RSS统计的包含了许多当前没有用到的页。
除非你的系统发生了交换,你不必过度担心各种内存统计数据。但是注意到持续增长的RSS可能意味着内存泄漏。
Java程序中,你应该更担心堆中发生了什么。总的内存使用大小是重要的,有几个办法可以去减小它。你更应该担心GC花费的时间,堆中的哪些部分被回收了。
读磁盘(比如数据库)是很花时间的,内存很便宜。加钱上内存 -_-//