在过去的一年里,我在应用程序的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参数:
-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
。 (对他们的答案进行投票!)以下是发生的事情:
我确定知道的事情:
java.nio
的文件访问后端。此后端将MappedByteBuffers
映射到文件本身。MappedByteBuffer.force()
pmap
列出:
最后一点是我的“Eureka!”时刻。
我的纠正措施:
MappedByteBuffer.force()
移动到数据库更新事件,而不是定期计时器。问题会神奇地消失吗?Java RSS memory used graph http://lanai.dietpizza.ch/images/stackoverflow-rss-problem-fixed.png
我可能有或没有时间弄清楚的问题:
MappedByteBuffer.force()
发生了什么?如果没有任何改变,它是否仍然写入整个文件?部分文件?是先加载吗?MappedByteBuffer.force()
移动到数据库更新事件而不是定期计时器,那问题会不会神奇地消失?答案 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”。它可能会解决您的问题。