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

有帮助吗?

解决方案

这是对 Java 的长期抱怨,但它基本上没有意义,并且通常基于查看错误的信息。通常的措辞类似于“Java 上的 Hello World 需要 10 兆字节!为什么需要这个?”好吧,这里有一种方法可以让 64 位 JVM 上的 Hello World 声称占用超过 4 GB 的空间......至少通过一种测量形式。

java -Xms1024m -Xmx4096m com.example.Hello

测量内存的不同方法

在 Linux 上, 顶部 命令为您提供了几个不同的内存数字。以下是关于 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 上,您可以使用 映射图 命令来查看映射到进程空间的所有内容(从这里开始我将只提及 Linux,因为它是我使用的;我确信 Windows 上有等效的工具)。这是“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”,它表示通过分配的内存块 映射.

从顶部开始,我们有

  • JVM 加载程序(即,当您键入 java)。这是非常小的;它所做的只是加载到存储真实 JVM 代码的共享库中。
  • 一堆保存 Java 堆和内部数据的匿名块。这是Sun JVM,因此堆被分成多代,每一代都是自己的内存块。请注意,JVM 根据以下方式分配虚拟内存空间: -Xmx 价值;这允许它有一个连续的堆。这 -Xms value 在内部使用来表示程序启动时有多少堆“正在使用”,并在接近该限制时触发垃圾回收。
  • 内存映射的jarfile,在这种情况下,保存“ 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 程序,关注堆中发生的情况要重要得多。消耗的空间总量很重要,您可以采取一些步骤来减少空间消耗。更重要的是您在垃圾收集上花费的时间以及堆的哪些部分被收集。

访问磁盘(即数据库)很昂贵,而内存很便宜。如果你可以用其中一种交换另一种,那就这样做吧。

其他提示

有与Java和glibc> = 2.10(包括Ubuntu的> = 10.04,RHEL> = 6)。

的已知问题

固化是设置这个ENV。变量:

export MALLOC_ARENA_MAX=4

如果您正在运行Tomcat,你可以添加这TOMCAT_HOME/bin/setenv.sh文件。

有关泊坞,这增加Dockerfile

ENV MALLOC_ARENA_MAX=4

有大约是设置MALLOC_ARENA_MAX一个IBM文章 https://www.ibm.com/developerworks/社区/博客/ kevgrig /进入/ linux_glibc_2_10_rhel_6_malloc_may_show_excessive_virtual_memory_usage?郎= EN

本博客文章说

  

驻留存储器已经知道以类似于的方式蠕变   内存泄漏或内存碎片。

有也是一个开放JDK错误 JDK-8193521“的glibc浪费内存使用缺省配置“

搜索MALLOC_ARENA_MAX在谷歌或SO更多的引用。

您可能需要调整还有其他的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进程的内存数量是相当多的,看齐什么我期望的那样。我已经在嵌入式/内存有限的系统上运行的Java类似的问题。运行的任意VM限制或没有足够数量的交换系统的任何的应用趋于破裂。这似乎是许多现代应用程序的性质是不是设计用于在资源有限的系统中使用。

您有几个选项,你可以尝试限制你的JVM的内存占用。这可能减少虚拟内存占用:

  

-XX:ReservedCodeCacheSize =32米保留代码高速缓存大小(以字节为单位) - 最大   代码缓存大小。 [64位Solaris,   AMD64和-server 86:48米;在   1.5.0_06和更早,64位Solaris和and64:1024米]

     

-XX:MaxPermSize参数=64米常驻代的大小。 [5.0和更新的:   64位的虚拟机被缩放大30%; 1.4   AMD64:96米; 1.3.1 -client:32立方米]

此外,您还应该将-Xmx(最大堆大小)设置为尽可能接近的实际的峰值内存使用量的值您的应用程序。我相信JVM的默认行为仍的的每一展开时,它到最高时的堆大小。如果你开始使用32M堆和您的应用程序达到顶峰至65M,那么堆将最终成长32M - > 64M - > 128M

您也可以尝试这个方法,使虚拟机较少攻击关于成长堆:

  

-XX:MinHeapFreeRatio = 40最低堆GC到后自由百分比   避免膨胀。

另外,从我从几年前这个实验还记得,装载本地库的数量对最低足迹产生巨大的影响。载入的java.net.Socket加入多于15M如果我记得正确(我可能不)。

太阳JVM需要大量的存储器,用于热点和它在运行时库映射在共享存储器中。

如果存储器是一个问题考虑使用适于嵌入另一个JVM。 IBM拥有J9,且有开源“jamvm”,它采用GNU的classpath库。也太阳对黑子所以有替代运行佳乐JVM。

只是一个想法,但你可以检查的一个ulimit -v选项

这是不是一个实际的解决方案,因为它会限制可用于的所有的过程,但是这将允许你检查你的应用程序的行为在有限的虚拟内存。

地址空间

减少与有限资源的系统的堆SICE可以是玩的-XX的一种方式:MaxHeapFreeRatio变量。这通常设置为70,并且是堆的最大百分比是免费的GC缩小它。它设置为较低的值,你会看到如jvisualvm分析器,小堆SICE通常用于你的程序。

编辑:为了对-XX设置小的值:MaxHeapFreeRatio还必须设置-XX:MinHeapFreeRatio 例如

java -XX:MinHeapFreeRatio=10 -XX:MaxHeapFreeRatio=25 HelloWorld

EDIT2:增加了对启动并执行相同的任务,一个用默认参数和一个用10和25作为参数的实际应用的示例。我没有注意到任何真实的速度差,虽然Java在理论上应该使用更多的时间来增加在后一种示例的堆。

“默认参数”

在结束时,最大堆是905,使用的堆为378

“MinHeap

在结束时,最大堆是722,使用的堆为378

这实际上有一些inpact,为我们的应用程序的远程桌面服务器上运行,可能有许多用户同时运行它。

Sun公司的Java 1.4具有以下参数来控制存储器大小:

  

-Xmsn       指定的初始大小,以字节为单位,分配的内存池。   此值必须是1024的倍数   大于1MB。追加字母k   或K来表示千字节,或m或M   表示兆字节。默认   值是2MB。例子:

           -Xms6291456
           -Xms6144k
           -Xms6m
     

-Xmxn       指定的最大大小,以字节为单位的内存分配池。   该值必须是1024的倍数   大于2MB。追加字母k   或K来表示千字节,或m或M   表示兆字节。默认   值是64MB。例子:

           -Xmx83886080
           -Xmx81920k
           -Xmx80m

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所需存储器的量。但是,请注意,这是虚拟内存,而不是居民,所以它只是在那里停留无伤害,如果没有实际使用。

Alernatively,你可以尝试一些其他的JVM然后太阳之一,拥有更小的内存占用,但我不能在这里提供意见。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top