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

时间:2009-10-23 11:50:40

标签: java memory jvm sun performance

在过去的一年里,我在应用程序的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 Heap Used Graph 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)
  • 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。


问题&溶液取值

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

我确定知道的事情:

  1. 我的应用程序使用JRobin 1.4记录并显示数据,这是我三年前编写到我的应用中的内容。
  2. 当前创建的应用程序最繁忙的实例
    1. 在启动后一小时内,超过1000个新的JRobin数据库文件(每个大约1.3MB)
    2. 启动后每天
    3. ~100 +
  3. 如果有要写的内容,应用程序每15秒更新一次这些JRobin数据库对象。
  4. 在默认配置JRobin中:
    1. 使用基于java.nio的文件访问后端。此后端将MappedByteBuffers映射到文件本身。
    2. 每五分钟一次JRobin守护程序线程在每个JRobin底层数据库MBB上调用MappedByteBuffer.force()
  5. pmap列出:
    1. 6500映射
    2. 其中5500个是1.3MB的JRobin数据库文件,大小为~7.1GB
  6. 最后一点是我的“Eureka!”时刻。

    我的纠正措施:

    1. 考虑更新到最新的JRobinLite 1.5.2,这显然更好
    2. 在JRobin数据库上实施适当的资源处理。目前,一旦我的应用程序创建了一个数据库,然后在数据库不再被主动使用后再也不会转储它。
    3. 尝试将MappedByteBuffer.force()移动到数据库更新事件,而不是定期计时器。问题会神奇地消失吗?
    4. 立即,将JRobin后端更改为java.io实现 - 换行符。这会慢一些,但可能不是问题。以下是显示此更改的直接影响的图表。
    5. Java RSS memory used graph http://lanai.dietpizza.ch/images/stackoverflow-rss-problem-fixed.png

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

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

4 个答案:

答案 0 :(得分:18)

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

修改 根据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 :(得分: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

(由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处理程序线程),每个应该只消耗一两页物理内存,即使每个都有半兆的提交费用。

答案 2 :(得分:3)

  

为什么会这样?发生了什么“引擎盖下”?

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

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

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

关于过多的线程创建,每个线程都有自己的堆栈,这可能非常大。堆栈大小实际上取决于VM,OS和架构,例如for JRockit在Linux x64上它是256K,我没有在Sun的文档中找到Sun的VM的参考。这直接影响线程内存(线程内存=线程堆栈大小*线程数)。如果您创建并销毁大量线程,则可能不会重用内存。

  

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

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

答案 3 :(得分:1)

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

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