TOP中的java内存

禄光霁
2023-12-01

这是一个翻译文章

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

  • VIRT is the virtual memory space: the sum of everything in the virtual memory map (see below). It is largely meaningless, except when it isn’t (see below).
    VIRT 是虚拟内存,它是虚拟内存中所有的映射,很大程度上没有意义,当它没意义的时候!
  • RES is the resident set size: the number of pages that are currently resident in RAM. In almost all cases, this is the only number that you should use when saying “too big.” But it’s still not a very good number, especially when talking about Java.
    RES是常驻集的大小,还在RAM中的页大小(缺页中断了解下?多级cache和cache大小关系了解下?),大多数情况下,当它大的时候,你才能说:“啊,内存占用太大啦!”,但这仍不是一个好的数字,特别是在谈论Java的时候。
  • SHR is the amount of resident memory that is shared with other processes. For a Java process, this is typically limited to shared libraries and memory-mapped JARfiles. In this example, I only had one Java process running, so I suspect that the 7k is a result of libraries used by the OS.
    SHR是那些和其他进程共享的常驻内存,对于Java进程来说,通常会限制共享库和内存映射 JAR文件。在这个例子中,我只运行了一个Java进程,所以我猜那7k是OS用了的原因。
  • SWAP isn’t turned on by default, and isn’t shown here. It indicates the amount of virtual memory that is currently resident on disk, whether or not it’s actually in the swap space. The OS is very good about keeping active pages in RAM, and the only cures for swapping are (1) buy more memory, or (2) reduce the number of processes, so it’s best to ignore this number.
    SWAP默认是关闭的,所以没有在这里显示,它表明了当前虚拟内存在磁盘上的那部分大小,无论它是不是真正的在交换空间。OS很擅长把活动的内存置于RAM中,解决交换的办法有二,加钱买内存或者少跑点程序吧,所以,忽略这个数字。

要解释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位操作系统很普遍了,我认为过不了多久就考虑到对虚拟内存大小的统计。

什么时候常驻内存集(RSS)大小是重要的?

常驻内存集大小是虚拟内存的一部分,在RAM中的那部分。如果你的常驻内存集增长到栈你的物理内存一定比例时,你要开始担心了。如果常驻内存集用完了你的物理内存,操作系统就要开始交换内存了,此时已经不是担心的时候了(是哭的时候?)。

但是RSS也会有误导,特别是负载较轻的机器。重新声明一个进程的页也不是很费劲。这样的做没啥好处,同时也有潜在的问题:在以后读取到该页导致的页错误。结果就是,RSS统计的包含了许多当前没有用到的页。

结束语

除非你的系统发生了交换,你不必过度担心各种内存统计数据。但是注意到持续增长的RSS可能意味着内存泄漏。
Java程序中,你应该更担心堆中发生了什么。总的内存使用大小是重要的,有几个办法可以去减小它。你更应该担心GC花费的时间,堆中的哪些部分被回收了。
读磁盘(比如数据库)是很花时间的,内存很便宜。加钱上内存 -_-//

another:https://www.jianshu.com/p/479a715d461e

 类似资料: