Java进程内存使用量不断增加
前提条件:
- 具有16 Gb RAM的PC
- 在Ubuntu 16.10 x64上安装了JDK 1.8.x.
- 标准的基于Spring的Web应用程序,部署在Tomcat 8.5.x上。 Tomcat配置了下一个参数:
CATALINA_OPTS="$CATALINA_OPTS -Xms128m -Xmx512m -XX:NewSize=64m -XX:MaxNewSize=128m -Xss512k -XX:+UseParallelGC -XX:+AggressiveOpts -XX:+UseFastAccessorMethods -XX:MaxMetaspaceSize=512m -XX:-TieredCompilation -XX:ReservedCodeCacheSize=512m"
- JMeter 2.13用于负载测试运行
- JProfiler 9.x用于java堆内存使用情况跟踪
-
top
util用于java进程内存使用情况跟踪
当我连续3次开始加载测试时,我观察到(使用top
)java进程增加了大量已用内存:
- Tomcat启动后使用~1Gb
- 在第一次测试运行后,它使用4.5Gb
- 当所有测试都完成后,Tomcat正在使用7Gb的RAM
所有这些时间堆大小都是有限的,JProfiler确认 – 堆大小不超过512Mb。
这是JProfiler的截图。 底部的红色数字是java进程使用的内存大小(根据top
)。
问题是: 为什么java进程在工作时始终不断增加内存使用量?
谢谢!
UPD#1:关于可能的重复:他们have confirmed that this only happens on Solaris.
但我使用的是Ubuntu 16.10。 同样,尖锐的问题没有一个可以解释问题原因的答案。
UPD#2:暂停一段时间后我不得不回到这个问题。 现在我使用pmap
util来转储java
进程使用的内存。 我有三个转储:在测试运行之前,在第一次测试执行之后和在执行一些N次测试之后。 测试它们会为应用程序产生大量流量。 所有转储都在这里: https : //gist.github.com/proshin-roman/752cea2dc25cde64b30514ed9ed9bbd0 。 它们非常庞大,但最有趣的东西是第8行,堆大小:测试前需要282.272 Kb
,最后需要282.272 Kb
– 差异超过10倍! 每次我进行测试时它都在增长。 同时堆大小是恒定的(根据JProfiler / VisualVM)。 我有什么选择才能找到导致此问题的原因? 调试JVM? 我试图找到任何方法来“看”这段内存但失败了。 所以:
- 我能以某种方式识别
[heap]
内存段的内容吗? - java的这种行为看起来如何?
我将很感激有关此问题的任何提示。 谢谢大家!
UPD#3 :使用jemalloc(感谢@ivan的想法)我得到了下一张图片:
看起来我的问题与此处描述的几乎相同: http : //www.evanjones.ca/java-native-leak-bug.html
UPD#4 :现在我发现问题与java.util.zip.Inflater / Deflater有关,这些类在我的应用程序的许多地方使用。 但是对内存消耗的最大影响使得与删除SOAP服务的交互。 我的应用程序使用JAX-WS标准的参考实现,它在负载下给出了下一个内存消耗(它在10Gb之后具有低精度): 然后我做了相同的负载测试,但是使用Apache CXF实现,它给出了下一个结果: 所以你可以看到CXF使用更少的内存并且它更稳定(它没有像ref.impl一样增长)。 最后我在JDK问题跟踪器上发现了一个问题 – https://bugs.openjdk.java.net/browse/JDK-8074108 – 这又是关于zip库中的内存泄漏问题,而且问题尚未解决。 所以看起来我无法真正解决我的应用程序中的内存泄漏问题,只是可以做一些解决方法。
感谢你的帮助!
我的假设是你在JProfiler中收集分配信息/调用栈/等,你观察到的RSS增长与JProfiler保持内存中的数据有关。
您可以通过收集较少的信息来validation这是否属实(在分析开始时应该有一个屏幕,允许您例如不收集对象分配),并查看您是否观察到较小的RSS增长。 在没有JProfiler的情况下运行负载测试也是一种选择。
我过去也遇到过类似的情况 。
你能用这个选项-XX:MaxDirectMemorySize=1024m
重新运行测试吗-XX:MaxDirectMemorySize=1024m
? 这个限制的确切值无关紧要,但它显示了可能的“泄漏” 。
您还可以提供GC详细信息( -XX:+PrintGC
)吗?
java.nio.ByteBuffer是它们可能的原因,因为它具体完成。
更新#1
我看到类似的行为有两个原因:java.misc.Unsafe(不太可能)和高负载的JNI调用。
没有测试的概况很难理解。
更新#2
高负载的JNI调用和finalize()方法都会导致所描述的问题,因为对象没有足够的时间来完成。
juzip.Inflater
片段如下:
/** * Closes the decompressor when garbage is collected. */ protected void finalize() { end(); } /** * Closes the decompressor and discards any unprocessed input. * This method should be called when the decompressor is no longer * being used, but will also be called automatically by the finalize() * method. Once this method is called, the behavior of the Inflater * object is undefined. */ public void end() { synchronized (zsRef) { long addr = zsRef.address(); zsRef.clear(); if (addr != 0) { end(addr); buf = null; } } } private native static void end(long addr);
基于Occam的剃刀:难道不是你有某个内存泄漏(即“无意识的对象保留”a’la Effective Java Item 6)?