为什么即使堆等大小稳定,Sun JVM 也会继续消耗更多的 RSS 内存?问题和解决方案:

2022-09-01 09:00:09

在过去的一年里,我对应用程序的Java堆使用率有了很大的改进- 减少了66%。为了做到这一点,我一直在通过SNMP监控各种指标,例如Java堆大小,cpu,Java非堆等。

最近,我一直在监视JVM有多少真实内存(RSS,驻留集),并且有些惊讶。JVM 消耗的实际内存似乎完全独立于我的应用程序堆大小、非堆、eden 空间、线程数等。

由 Java SNMP Java Heap Used Graph http://lanai.dietpizza.ch/images/jvm-heap-used.png 测量的堆大小

真实内存(以 KB 为单位)。(例如:1 MB 的 KB = 1 GB)Java 堆使用的图形 http://lanai.dietpizza.ch/images/jvm-rss.png

(堆图中的三个斜率对应于应用程序更新/重新启动。

这对我来说是一个问题,因为JVM消耗的所有额外内存都是“窃取”操作系统可用于文件缓存的内存。事实上,一旦RSS值达到~2.5-3GB,我开始看到我的应用程序响应时间变慢,CPU利用率更高,主要是IO等待。因为某些点对交换分区的分页开始生效。这都是非常不可取的。

所以,我的问题:

  • 为什么会发生这种情况?“引擎盖下”发生了什么?
  • 我能做些什么来控制JVM的实际内存消耗?

血腥的细节:

  • RHEL4 64 位 (Linux - 2.6.9-78.0.5.ELsmp #1 SMP 星期三 九月 24 ...2008 x86_64...GNU/Linux)
  • Java 6 (build 1.6.0_07-b06)
  • 雄猫 6
  • 应用程序(点播 HTTP 视频流)
    • 通过 java.nio FileChannels 实现高 I/O
    • 数百到低数千个线程
    • 数据库使用率低
    • 春天,冬眠

相关 JVM 参数:

-Xms128m  
-Xmx640m  
-XX:+UseConcMarkSweepGC  
-XX:+AlwaysActAsServerClassMachine  
-XX:+CMSIncrementalMode    

-XX:+PrintGCDetails 
-XX:+PrintGCTimeStamps  
-XX:+PrintGCApplicationStoppedTime  
-XX:+CMSLoopWarn  
-XX:+HeapDumpOnOutOfMemoryError 

我如何测量 RSS:

ps x -o command,rss | grep java | grep latest | cut -b 17-

这进入一个文本文件,并定期读入监控系统的RRD数据库。请注意,ps 输出千字节。


问题和解决方案:

虽然最终是ATorras的答案被证明是最终正确的,但kdgregory通过使用.kdgregory引导我走上正确的诊断路径。(去投票给他们的答案!以下是正在发生的事情:pmap

我确切知道的事情:

  1. 我的应用程序使用JRobin 1.4记录和显示数据,这是我三年前在我的应用程序中编码的东西。
  2. 当前创建的应用程序最忙的实例
    1. 在启动后的一小时内,超过1000个新的JRobin数据库文件(每个约1.3MB)
    2. 启动后每天约 100+
  3. 该应用程序每15秒更新一次这些JRobin数据库对象,如果有东西要写的话。
  4. 在默认配置JRobin中:
    1. 使用基于文件访问的后端。此后端映射到文件本身。java.nioMappedByteBuffers
    2. 每五分钟一次,一个JRobin守护进程线程调用每个JRobin底层数据库MBBMappedByteBuffer.force()
  5. pmap上市:
    1. 6500 映射
    2. 其中5500个是1.3MB JRobin数据库文件,最高可达约7.1GB

最后一点是我的“尤里卡!”时刻。

我的纠正措施:

  1. 考虑更新到最新的JRobinLite 1.5.2,这显然更好
  2. 在 JRobin 数据库上实现正确的资源处理。目前,一旦我的应用程序创建了一个数据库,然后在数据库不再被主动使用后永远不会转储它。
  3. 尝试将 移动到数据库更新事件,而不是定期计时器。问题会神奇地消失吗?MappedByteBuffer.force()
  4. 立即将JRobin后端更改为 java.io 实现 - 行更改。这会慢一些,但可能不是问题。下图显示了此更改的直接影响。

Java RSS 内存使用的图形 http://lanai.dietpizza.ch/images/stackoverflow-rss-problem-fixed.png

我可能有时间或可能没有时间弄清楚的问题:

  • JVM 内部发生了什么?如果没有任何更改,它是否仍会写入整个文件?文件的一部分?它会先加载它吗?MappedByteBuffer.force()
  • RSS 中是否始终存在一定数量的 MBB?(RSS 大约是分配的 MBB 总大小的一半。巧合?我怀疑不是。
  • 如果我移动到数据库更新事件,而不是定期计时器,问题会神奇地消失吗?MappedByteBuffer.force()
  • 为什么 RSS 斜率如此规律?它与任何应用程序负载指标都不相关。

答案 1

只是一个想法:NIO缓冲区放置在JVM之外。

编辑:根据2016年,值得考虑@Lari Hotari评论[为什么即使堆等大小稳定,Sun JVM也会继续消耗更多的RSS内存?]因为回到2009年,RHEL4的glibc<2.10(~2.3)

问候。


答案 2

RSS 表示正在使用的页 - 对于 Java,它主要是堆中的活动对象和 JVM 中的内部数据结构。除了使用更少的对象或进行较少的处理之外,您无法执行太多操作来减小其大小。

就你而言,我不认为这是一个问题。该图似乎显示消耗了3兆,而不是您在文本中写的3 gig。这真的很小,不太可能导致分页。

那么,您的系统中还发生了什么呢?您是否有很多Tomcat服务器,每个服务器消耗3M的RSS?你扔了很多GC标志,它们是否表明该过程将大部分时间花在GC上?您是否在同一台计算机上运行数据库?

编辑以回复评论

关于3M RSS大小 - 是的,对于Tomcat进程来说,这似乎太低了(我选中了我的框,并在89M处有一个已经有一段时间没有激活了)。但是,我不一定期望它是>堆大小,我当然也不期望它是堆大小的近5倍(你使用-Xmx640) - 它最坏的情况应该是堆大小+一些每个应用程序的常量。

这让我怀疑你的数字。因此,请运行以下命令以获取快照(将7429替换为您正在使用的任何进程ID),而不是随时间变化的图形):

ps -p 7429 -o pcpu,cutime,cstime,cmin_flt,cmaj_flt,rss,size,vsize

(由Stu编辑,以便我们可以将格式化的结果设置为上述ps信息请求:)

[stu@server ~]$ ps -p 12720 -o pcpu,cutime,cstime,cmin_flt,cmaj_flt,rss,size,vsize
%CPU - - - -  RSS SZ  VSZ
28.8 - - - - 3262316 1333832 8725584

编辑以向后代解释这些数字

如前所述,RSS 是驻留集大小:物理内存中的页。SZ持有进程可写的页数(提交费用);手册页将此值描述为“非常粗糙”。VSZ 保存进程的虚拟内存映射的大小:可写页加上共享页。

通常,VSZ>SZ,并且非常>RSS。此输出指示非常不寻常的情况。

详细说明为什么唯一的解决方案是减少对象

RSS 表示驻留在 RAM 中的页面数 -- 主动访问的页面。使用Java,垃圾回收器将定期遍历整个对象图。如果此对象图占用了堆空间的大部分,则收集器将触及堆中的每个页面,要求所有这些页面都驻留在内存中。GC 非常擅长在每个主要集合之后压缩堆,因此,如果您使用部分堆运行,则大多数页面不需要在 RAM 中。

以及其他一些选项

我注意到你提到有数百到低的数千个线程。这些线程的堆栈也会添加到 RSS 中,尽管它应该不多。假设线程具有较浅的调用深度(通常用于应用服务器处理程序线程),则每个线程应仅消耗一页或两页的物理内存,即使每个线程都有半兆字节的提交费用。


推荐