为什么即使堆等大小稳定,Sun JVM也会继续消耗更多的RSS内存?

Stu*_*son 33 java memory performance jvm sun

在过去的一年里,我在应用程序的Java堆使用方面做了很大的改进 - 减少了66%.为此,我一直在通过SNMP监控各种指标,例如Java堆大小,cpu,Java非堆等.

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

堆栈大小由Java SNMP Java堆使用图表测量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 Wed Sep 24 ... 2008 x86_64 ... GNU/Linux)
  • Java 6(build 1.6.0_07-b06)
  • 雄猫6
  • 应用程序(按需HTTP视频流)
    • 通过java.nio FileChannels实现高I/O.
    • 数百到数千个线程
    • 数据库使用率低
    • 春天,Hibernate

相关的JVM参数:

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

-XX:+PrintGCDetails 
-XX:+PrintGCTimeStamps  
-XX:+PrintGCApplicationStoppedTime  
-XX:+CMSLoopWarn  
-XX:+HeapDumpOnOutOfMemoryError 
Run Code Online (Sandbox Code Playgroud)

我如何衡量RSS:

ps x -o command,rss | grep java | grep latest | cut -b 17-
Run Code Online (Sandbox Code Playgroud)

这将进入一个文本文件,并定期读入我的监控系统的RRD数据库.请注意,ps输出Kilo Bytes.


存在的问题及解决方案小号:

虽然最终是ATorras的答案证明最终是正确的,但是kdgregory通过使用引导我到正确的诊断路径pmap.(对他们的答案进行投票!)以下是发生的事情:

我确切知道的事情:

  1. 我的应用程序使用JRobin 1.4记录和显示数据,这是我三年前编写到我的应用程序中的内容.
  2. 当前创建的应用程序最繁忙的实例
    1. 在启动后的一小时内,有1000多个新的JRobin数据库文件(每个大约1.3MB)
    2. 启动后每天约100 +
  3. 如果有要写的东西,应用程序每15秒更新一次这些JRobin数据库对象.
  4. 在默认配置JRobin中:
    1. 使用java.nio基于文件的访问后端.此后端映射MappedByteBuffers到文件本身.
    2. JRobin守护程序线程每五分钟调用MappedByteBuffer.force()一次JRobin底层数据库MBB
  5. pmap 上市:
    1. 6500映射
    2. 其中5500个是3.1MB的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斜率如此规律?它与任何应用程序负载指标都不相关.

ATo*_*ras 18

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

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

问候.

  • 反对这一点的论点是**a)**RSS图形具有惊人的规则,直线斜率,并且**b)**"FileChannel"用法与应用程序的繁忙程度有关,该程序在一小时内波动很大 - 每小时,每天.我希望看到一个相关性. (3认同)

kdg*_*ory 14

RSS表示正在使用的页面 - 对于Java,它主要是堆中的活动对象,以及JVM中的内部数据结构.除了使用更少的对象或更少的处理之外,你可以做很多事情来减小它的大小.

在你的情况下,我不认为这是一个问题.当您在文本中写入时,图表似乎显示消耗3兆,而不是3演出.这真的很小,不太可能导致分页.

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

编辑以回应评论

关于3M RSS大小 - 是的,对于Tomcat进程来说这似乎太低了(我检查了我的盒子,并且在89M处有一个没有活动一段时间).不过,我并不指望它>堆大小,我当然不希望它是近5倍堆大小(你使用-Xmx640) - 它应该在最坏的情况是堆的大小+一些每个应用不变.

这让我怀疑你的号码.因此,请运行以下内容以获取快照(使用您正在使用的任何进程ID替换7429),而不是随时间变化的图表:

ps -p 7429 -o pcpu,cutime,cstime,cmin_flt,cmaj_flt,rss,size,vsize
Run Code Online (Sandbox Code Playgroud)

(由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
Run Code Online (Sandbox Code Playgroud)

编辑为后代解释这些数字

如上所述,RSS是驻留集大小:物理内存中的页面.SZ保存进程可写的页数(提交费用); 该联机帮助页将此值描述为"非常粗糙".VSZ保存进程的虚拟内存映射的大小:可写页面和共享页面.

通常,VSZ略微> SZ,非常> RSS.此输出表明非常不寻常的情况.

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

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

还有其他一些选择

我注意到你提到有数百到数千个线程.这些线程的堆栈也会添加到RSS中,尽管它不应该太多.假设线程有一个浅调用深度(通常用于应用服务器处理线程),每次只消耗一两页的物理内存,即使有一个半兆承诺费为每.