当前位置:  开发笔记 > 编程语言 > 正文

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

如何解决《Linux下的Java虚拟内存使用情况,使用的内存过多》经验,为你挑选了4个好方法。

我在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:系统中运行了几个其他应用程序,它们是嵌入式的.系统确实有虚拟内存限制(来自评论,重要细节).



1> kdgregory..:

这一直是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

VIRT是虚拟内存空间:虚拟内存映射中所有内容的总和(见下文).它基本上没有意义,除非它不是(见下文).

RES是驻留集大小:当前驻留在RAM中的页数.在几乎所有情况下,这是唯一一个在说"太大"时应该使用的数字.但它仍然不是一个非常好的数字,特别是在谈论Java时.

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

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

Windows任务管理器的情况有点复杂.在Windows XP下,有"内存使用"和"虚拟内存大小"列,但官方文档没有说明它们的含义.Windows Vista和Windows 7添加了更多列,它们实际上已经记录在案.其中,"工作集"测量是最有用的; 它大致对应于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分配的内存块.

从顶部开始,我们有

JVM加载程序(即,键入时运行的程序java).这个很小; 它所做的就是加载存储真实JVM代码的共享库.

一堆匿名阻止持有Java堆和内部数据.这是一个Sun JVM,因此堆被分成多代,每一代都是它自己的内存块.请注意,JVM根据-Xmx值分配虚拟内存空间; 这允许它有一个连续的堆.该-Xms值在内部用于表示程序启动时有多少堆"正在使用",并在接近该限制时触发垃圾收集.

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

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


您应该考虑到RES测量中缺少当前被换出的内存部分.因此,您可能具有较低的RES值,但仅仅是因为应用程序处于非活动状态并且大部分堆已交换到磁盘.Java在交换方面做得非常糟糕:在每个完整的GC上,大部分堆都被遍历和复制,因此如果你的大部分堆都在交换中,那么GC必须将它全部加载到主内存中.
@Jeach - 我很惊讶任何交换被报告,所以启动我的"旅行Linux"(一个Ubuntu 10.04的拇指驱动器,没有交换).当我在*top*中启用"SWAP"列时,我看到Eclipse有509m.当我用*pmap*查看它时,总虚拟空间为650米.所以我怀疑"SWAP"图表代表所有磁盘上的页面,而不仅仅是那些不在内存中的页面.
至于你的第二个问题:如果你经常从闪存卡读取页面,你的IO等待时间(在*top*的摘要中显示为"%wa")应该很高.但要注意,这对于任何活动都会很高,尤其是写入(假设您的程序可以执行任何操作).

2> Lari Hotari..:

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的IBM文章 https://www.ibm.com/developerworks/community/blogs/kevgrig/entry/linux_glibc_2_10_rhel_6_malloc_may_show_excessive_virtual_memory_usage?lang=en

这篇博文说

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

还有一个开放的JDK错误JDK-8193521"glibc浪费了默认配置的内存"

在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 8中存在一个JVM错误,导致无限制的本机内存增长:http://bugs.java.com/bugdatabase/view_bug.do?video_id = JDK-8164293 - 如果这对您有影响,可以使用`MALLOC_ARENA_MAX`减慢你的记忆力增长,但不能完全解决问题.
Java 8u131包含相关JVM错误的后向错误修复JDK-8164293 https://bugs.openjdk.java.net/browse/JDK-8178124.

3> James Schek..:

为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(我可能没有).



4> Thorbjørn Ra..:

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

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

推荐阅读
虎仔球妈_459
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有