首页 文章

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

提问于
浏览
32

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

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

Heap Size as measured by Java SNMP Java Heap Used Graph http://lanai.dietpizza.ch/images/jvm-heap-used.png

Real Memory in KB. (E.g.: 1 MB of KB = 1 GB) Java Heap Used Graph http://lanai.dietpizza.ch/images/jvm-rss.png

(堆图中的三个凹陷对应于应用程序更新/重新启动 . )

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

So, my questions:

  • Why is this happening? What is going on "under the hood"?

  • What can I do to keep the JVM's real memory consumption in check?

血腥的细节:

  • 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)

  • Tomcat 6

  • 应用程序(按需HTTP视频流)

  • 通过java.nio FileChannels进行高I / O.

  • 数百到数千个线程

  • 数据库使用率低

  • Spring,Hibernate

相关的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输出Kilo Bytes .


问题与解决方案:

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

Things I know for sure:

  • 我的应用程序使用JRobin 1.4记录和显示数据,这是我三年前编写到我的应用程序中的内容 .

  • 当前创建的应用程序最繁忙的实例

  • 在启动后的一小时内,超过1000个新的JRobin数据库文件(每个大约1.3MB)
    启动后每天

  • ~100

  • 如果有要写的内容,应用程序每15秒更新一次这些JRobin数据库对象 .

  • 在默认配置JRobin中:

  • 使用基于 java.nio 的文件访问后端 . 此后端将 MappedByteBuffers 映射到文件本身 .

  • 每五分钟一次JRobin守护程序线程在每个JRobin底层数据库MBB上调用 MappedByteBuffer.force()

  • pmap 已列出:

  • 6500映射

  • 5500其中有1.3MB的JRobin数据库文件,大小约为7.1GB

最后一点是我的"Eureka!"时刻 .

My corrective actions:

  • 考虑更新到最新的JRobinLite 1.5.2,这显然更好

  • 在JRobin数据库上实施适当的资源处理 . 目前,一旦我的应用程序创建了一个数据库,然后在数据库不再被主动使用之后再也不会转储它 .

  • 尝试将 MappedByteBuffer.force() 移动到数据库更新事件,而不是定期计时器 . 这个问题会神奇地消失吗?

  • Immediately ,将JRobin后端更改为java.io实现 - 换行符 . 这会慢一些,但可能不是问题 . 以下是显示此更改的直接影响的图表 .

Java RSS memory used graph http://lanai.dietpizza.ch/images/stackoverflow-rss-problem-fixed.png

Questions that I may or may not have time to figure out:

  • 使用 MappedByteBuffer.force() 在JVM内部发生了什么?如果没有任何改变,它是否仍然写入整个文件?部分文件?它首先加载吗?

  • 始终在RSS中是否有一定数量的MBB? (RSS大约是MBB总分配量的一半 . 巧合?我怀疑没有 . )

  • 如果我将 MappedByteBuffer.force() 移动到数据库更新事件而不是定期计时器,问题会不会消失?

  • 为什么RSS斜率如此规律?它与任何应用程序负载指标都不相关 .

4 回答

  • 18

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

    EDIT: 截至2016年,值得考虑@Lari Hotari评论[Why does the Sun JVM continue to consume ever more RSS memory even when the heap, etc sizes are stable?]因为回到2009年,RHEL4的glibc <2.10(~2.3)

    问候 .

  • 1

    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
    

    (由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中,尽管它不应该太多 . 假设线程具有浅的调用深度(典型的app-server处理程序线程),每个应该只消耗一两页物理内存,即使每个都有半兆的提交费用 .

  • 14

    为什么会这样?什么是“引擎盖下”?

    JVM使用的内存多于堆 . 例如,Java方法,线程堆栈和本机句柄分配在与堆不同的内存中,以及JVM内部数据结构中 .

    在您的情况下,可能的麻烦原因可能是:NIO(已经提到),JNI(已经提到过),过多的线程创建 .

    关于JNI,你写的是应用程序没有使用JNI但是...你使用什么类型的JDBC驱动程序?可能是2型,漏水吗?虽然你说数据库使用率很低,但这种可能性很小 .

    关于过多的线程创建,每个线程都有自己的堆栈,这可能非常大 . 堆栈大小实际上取决于VM,OS和架构,例如对于JRockit它's 256K on Linux x64, I didn't在Sun 's documentation for Sun' VM中找到引用 . 这直接影响线程内存(线程内存=线程堆栈大小*线程数) . 如果你创建并销毁大量线程,内存可能不会被重用 .

    我可以做些什么来控制JVM的实际内存消耗?

    说实话,数百到数千个线程对我来说似乎很大 . 也就是说,如果你真的需要那么多线程,可以通过 -Xss 选项配置线程堆栈大小 . 这可以减少内存消耗 . 但我认为这不会解决整个问题 . 当我查看真实的内存图时,我倾向于认为某处存在泄漏 .

  • 3

    众所周知,Java中的当前垃圾收集器不释放已分配的内存,尽管不再需要内存 . 但是,很奇怪,虽然您的堆大小限制为640MB,但您的RSS大小增加到> 3GB . 您是否在应用程序中使用任何本机代码,或者您是否已启用Tomcat的本机性能优化包?在这种情况下,您当然可能在代码或Tomcat中存在本机内存泄漏 .

    借助Java 6u14,Sun推出了新的“Garbage-First”垃圾收集器,如果不再需要,它可以将内存释放回操作系统 . 它仍被归类为实验性的,默认情况下未启用,但如果它是一个可行的选项,我会尝试升级到最新的Java 6版本并使用命令行参数启用新的垃圾收集器“-XX:UnlockExperimentalVMOptions -XX :UseG1GC“ . 它可能会解决您的问题 .

相关问题