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

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

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

是否可以为Linux下的Java进程配置正在使用的虚拟内存?

Edit 1:问题不在于堆.问题是,例如,如果我将堆设置为128MB,Linux仍然会分配210MB的虚拟内存,这是永远不需要的**

Edit 2:使用ulimit -v可以限制虚拟内存的数量.如果大小集小于204 MB,则应用程序将不会运行,即使它不需要204 MB,只需要64 MB.所以我想理解为什么Java需要这么多虚拟内存.这能改变吗?

Edit 3:嵌入式系统中还运行着其他几个应用程序.而且系统确实有虚拟内存限制(来自 comments 、重要详细信息).

推荐答案

这是Java长期以来的抱怨,但基本上没有意义,而且通常是基于查看错误的信息.通常的说法是"Hello World on Java需要10兆字节!为什么它需要这个?"好吧,下面是一种在64位JVM上创建Hello World的方法,声称它可以占用4GB以上的容量...至少通过一种形式的测量.

java -Xms1024m -Xmx4096m com.example.Hello

Different Ways to Measure Memory

在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进程,这通常仅限于共享库和内存映射文件.在这个例子中,我只运行了一个Java进程,所以我怀疑7k是操作系统使用库的结果.
  • 默认情况下不会启用交换,此处也不会显示交换.它表示当前驻留在磁盘上的虚拟内存量,即whether or not it's actually in the swap space.操作系统非常擅长将活动页面保存在RAM中,交换的唯一解决方法是(1)购买更多内存,或(2)减少进程数量,因此最好忽略这个数字.

Windows任务管理器的情况要复杂一些.在Windows XP下,有"内存使用情况"和"虚拟内存大小"列,但official documentation没有说明它们的含义.Windows Vista和Windows 7添加了更多的列,它们实际上是documented.其中,"工作集"测量是最有用的;它大致相当于Linux上RES和SHR的总和.

Understanding the Virtual Memory Map

进程消耗的虚拟内存是进程内存映射中所有内容的总和.这包括数据(例如Java堆),但也包括程序使用的所有共享库和内存映射文件.在Linux上,您可以使用pmap命令查看映射到进程空间的所有内容(从这里开始,我只会提到Linux,因为我使用的是Linux;我相信Windows上也有类似的工具).以下是"Hello World"项目记忆 map 的节选;整个内存映射的长度超过pmap行,而且有pmap0行列表并不罕见.

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代码的共享库中.
  • 一堆anon块保存Java堆和内部数据.这是一个Sun JVM,因此堆被分为多个代,每个代都是自己的内存块.请注意,JVM基于-Xmx值分配虚拟内存空间;这允许它有一个连续的堆.-Xms值在内部用于表示程序启动时有多少堆处于"使用中",并在接近该限制时触发垃圾收集.
  • 内存映射的JAR文件,在本例中是保存"JDK类"的文件在内存映射JAR时,可以非常高效地访问其中的文件(而不是每次从一开始就读取).Sun JVM将内存映射类路径上的所有JAR;如果应用程序代码需要访问JAR,还可以对其进行内存映射.
  • 两个线程的每线程数据.StackOverFlowError万的挡路就是线程堆栈.我对4k的挡路没有很好的解释,但是@ericsoe认为它是一个"守卫挡路":它没有读/写权限,所以如果被访问就会导致段错误,jvm捕捉到这一点并将其翻译成StackOverFlowError.对于一个真正的应用程序,您将在内存映射中看到数十个(如果不是数百个)这样的条目重复出现.
  • 保存实际JVM代码的共享库之一.有好几种.
  • C标准库的共享库.这只是JVM加载的严格意义上不属于Java的众多内容之一.

共享库特别有趣:每个共享库至少有两个段:一个只读段包含库代码,另一个读写段包含库的全局进程数据(我不知道没有权限的段是什么;我只在x64 Linux上见过).库的只读部分可以在使用库的所有进程之间共享;例如,libc有150万个可共享的虚拟内存空间.

When is Virtual Memory Size Important?

虚拟内存映射包含很多东西.其中一些是只读的,一些是共享的,还有一些是分配的,但从未接触过(例如,本例中几乎所有4Gb的堆).但操作系统足够智能,只需加载所需的内容,因此虚拟内存大小在很大程度上无关紧要.

虚拟内存大小很重要的一点是,如果在32位操作系统上运行,则只能分配2Gb(或在某些情况下,3Gb)的进程地址空间.在这种情况下,您要处理的是一个稀缺的资源,可能需要做出权衡,例如减少堆大小,以便内存映射一个大文件或创建大量线程.

但是,考虑到64位机器无处不在,我认为虚拟内存大小很快就会成为一个完全不相关的统计数据.

When is Resident Set Size Important?

常驻集大小是虚拟内存空间中实际位于RAM中的部分.如果你的RSS在你的总物理内存中占据了相当大的一部分,那么也许是时候开始担心了.如果你的RSS增长到占用你所有的物理内存,你的系统开始交换,那么现在就不是开始担心的时候了.

但RSS也有误导性,尤其是在负载较轻的机器上.操作系统不会花费大量精力回收进程使用的页面.这样做没有什么好处,而且如果进程在将来触及页面,可能会出现代价高昂的页面错误.因此,RSS统计数据可能包含大量未被使用的页面.

Bottom Line

除非你是在交换,否则不要过分担心各种内存统计数据告诉你什么.警告:不断增长的RSS可能意味着某种内存泄漏.

对于Java程序,关注堆中发生的事情要重要得多.占用的总空间量很重要,您可以采取一些步骤来减少它.更重要的是您在垃圾收集上花费的时间,以及正在收集堆的哪些部分.

访问磁盘(即数据库)很昂贵,内存也很便宜.如果你可以用一个换另一个,那么就这样做.

Java相关问答推荐

获取拦截器内部的IP地址

JPackaged应用程序启动MSI调试,然后启动System. exit()

如何使用jooq generator将表名和列名映射为人类可读的?

Java自定义ThreadPool—暂停任务提交并取消当前排队任务

如何在带有Micronaut的YAML中使用包含特殊字符的字符串作为键

Spring Boot@Cachebale批注未按预期工作

无法了解Java线程所消耗的时间

如何从JNI方法正确调用NSOpenPanel以在正确的线程上运行?

使用UTC时区将startDatetime转换为本地时间

使用htmlunit和java单击按钮

在Oracle JDBC连接中,连接失效和身份验证失效是什么意思?

Java堆中的许多java.time.ZoneRegion实例.ZoneId实例不应该被缓存吗?

如何获得凌空cookies ,并设置它在下一个请求- android

Quarkus:运行时出现EnumConstantNotPresentException

如果第一位数字和最后一位数字相差超过一位,您将如何获得随机数?

找出承载Cargo 的最小成本

无限递归Java问题

使用同步方法中的新线程调用同步方法

将基于实例编号的对象列表拆分为新的对象列表

Hibernate 命名策略导致 Java Spring Boot 应用程序中出现未知列错误