我在Linux下运行的Java应用程序有问题 .
当我使用默认的最大堆大小(64 MB)启动应用程序时,我看到使用tops应用程序为应用程序分配了240 MB的虚拟内存 . 这会在计算机上创建一些其他软件的问题,这些软件相对资源有限 .
据我所知,无论如何都不会使用保留的虚拟内存,因为一旦达到堆限制,就会抛出 OutOfMemoryError
. 我在Windows下运行相同的应用程序,我发现虚拟内存大小和堆大小相似 .
无论如何我可以在Linux下配置用于Java进程的虚拟内存吗?
Edit 1 :问题不在于堆 . 问题是,如果我设置一个128 MB的堆,那么Linux仍然会分配210 MB的虚拟内存,这是不需要的 . **
Edit 2 :使用 ulimit -v
允许限制虚拟内存量 . 如果大小设置低于204 MB,则应用程序赢得't run even though it doesn' t需要204 MB,仅64 MB . 所以我想理解为什么Java需要这么多虚拟内存 . 这可以改变吗?
Edit 3 :系统中还运行了其他几个应用程序,这些应用程序是嵌入式的 . 系统确实有虚拟内存限制(来自评论,重要细节) .
8 回答
这一直是Java的长期抱怨,但它在很大程度上毫无意义,通常基于查看错误的信息 . 通常的措辞就像“Java上的Hello World需要10兆字节!为什么需要它?”好吧,这是一种让64位JVM上的Hello World占用超过4千兆字节的方法......至少通过一种测量形式 .
测量记忆的不同方法
在Linux上,top命令为您提供了几个不同的内存编号 . 以下是关于Hello World示例的内容:
VIRT是虚拟内存空间:虚拟内存映射中所有内容的总和(见下文) . 它几乎没有意义,除非它不是(见下文) .
RES是驻留集大小:当前驻留在RAM中的页数 . 在几乎所有情况下,这是你说"too big."时应该使用的唯一数字 . 但它仍然不是一个非常好的数字,特别是在谈论Java时 .
SHR是与其他进程共享的驻留内存量 . 对于Java进程,这通常仅限于共享库和内存映射的JAR文件 . 在这个例子中,我只运行了一个Java进程,所以我怀疑7k是操作系统使用的库的结果 .
SWAP isn 't turned on by default, and isn' t此处显示 . 它表示当前驻留在磁盘上的虚拟内存量,无论它是否实际位于交换空间中 . 操作系统非常适合将活动页面保存在RAM中,并且交换的唯一方法是(1)购买更多内存,或(2)减少进程数量,因此最好忽略此数字 .
Windows任务管理器的情况有点复杂 . 在Windows XP下,有"Memory Usage"和"Virtual Memory Size"列,但official documentation对它们的含义保持沉默 . Windows Vista和Windows 7添加了更多列,它们实际上是documented . 其中,"Working Set"测量是最有用的;它大致对应于Linux上RES和SHR的总和 .
了解虚拟内存映射
进程消耗的虚拟内存是进程内存映射中所有内容的总和 . 这包括数据(例如,Java堆),但也包括程序使用的所有共享库和内存映射文件 . 在Linux上,您可以使用pmap命令查看映射到进程空间的所有内容(从此处开始我使用的是'm only going to refer to Linux, because it'我是'm sure there are equivalent tools for Windows). Here' s摘自"Hello World"程序的内存映射;整个内存映射超过100行,拥有千行列表并不罕见 .
格式的快速说明:每行以段的虚拟内存地址开头 . 接下来是段大小,权限和段的来源 . 最后一项是文件或"anon",表示通过mmap分配的内存块 .
从顶部开始,我们有
JVM加载程序(即,当您键入
java
时运行的程序) . 这个很小;它所做的就是加载存储真实JVM代码的共享库 .一堆匿名阻止了Java堆和内部数据 . 这是一个Sun JVM,因此堆被分成多代,每一代都是它自己的内存块 . 请注意,JVM根据
-Xmx
值分配虚拟内存空间;这允许它有一个连续的堆 .-Xms
值在内部用于表示程序启动时有多少堆"in use",并在接近该限制时触发垃圾收集 .内存映射的JAR文件,在本例中是文件保存"JDK classes."当您对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程序,注意堆中发生的事情更为重要 . 消耗的空间总量很重要,您可以采取一些措施来减少这种情况 . 更重要的是您在垃圾收集中花费的时间,以及收集堆的哪些部分 .
访问磁盘(即数据库)是昂贵的,并且内存便宜 . 如果你可以换一个换另一个,那就这样做吧 .
只是一个想法,但你可以检查a ulimit -v option的影响 .
这不是一个实际的解决方案,因为它会限制所有进程可用的地址空间,但这将允许您使用有限的虚拟内存检查应用程序的行为 .
Sun的java 1.4具有以下控制内存大小的参数:
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,内存占用更少,但我不能在这里提出建议 .
减少具有有限资源的系统的堆栈的一种方法可能是使用-XX:MaxHeapFreeRatio变量 . 这通常设置为70,并且是GC缩小之前可用的堆的最大百分比 . 将其设置为较低的值,您将在例如jvisualvm分析器中看到通常将较小的堆sice用于您的程序 .
编辑:要为-XX:MaxHeapFreeRatio设置小值,您还必须设置-XX:MinHeapFreeRatio例如
EDIT2:添加了一个真实应用程序的示例,该应用程序启动并执行相同的任务,一个使用默认参数,另一个使用10和25作为参数 . 我没有注意到任何真正的速度差异,虽然理论上java应该在后一个例子中使用更多的时间来增加堆 .
最后,最大堆是905,使用堆是378
最后,最大堆为722,使用堆为378
这实际上有一些影响,因为我们的应用程序在远程桌面服务器上运行,许多用户可以立即运行它 .
为Java进程分配的内存量与我的预期相当 . 我在嵌入式/内存有限的系统上运行Java时遇到了类似的问题 . 运行具有任意VM限制的任何应用程序,或者运行设计为在资源有限的系统上使用的系统 .
您可以尝试更多选项来限制JVM的内存占用 . 这可能会减少虚拟内存占用量:
此外,还应将-Xmx(最大堆大小)设置为尽可能接近应用程序的 actual peak memory usage 的值 . 我相信每次将JVM扩展到最大值时,JVM的默认行为仍然会使堆大小加倍 . 如果你从32M堆开始,你的应用程序达到峰值65M,那么堆将最终增长32M - > 64M - > 128M .
您也可以尝试这样做以使VM不那么积极地增加堆:
此外,从我几年前的实验中回忆起,加载的本机库数量对最小占用空间产生了巨大影响 . 如果我没记错的话,加载java.net.Socket增加了超过15M(我可能没有) .
Java和glibc> = 2.10存在已知问题(包括Ubuntu> = 10.04,RHEL> = 6) .
治愈就是设定这个环境 . 变量:
export MALLOC_ARENA_MAX=4
如果您正在运行Tomcat,则可以将其添加到TOMCAT_HOME/bin/setenv.sh
文件中 .有一篇关于设置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文章
This blog post says
已知驻留内存以类似于内存泄漏或内存碎片的方式蠕变 .
在Google或SO上搜索MALLOC_ARENA_MAX以获取更多参考资料 .
您可能还想调整其他malloc选项以优化分配内存的低碎片:
Sun JVM需要大量内存用于HotSpot,并且它映射到共享内存中的运行时库中 .
如果内存是一个问题,请考虑使用另一个适合嵌入的JVM . IBM有j9,还有开源“jamvm”,它使用GNU类路径库 . Sun还在SunSPOTS上运行了Squeak JVM,因此有其他选择 .