Java进程内存使用量无限增长

时间:2016-11-18 08:30:03

标签: java tomcat memory

前提条件:

  • 具有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 for java heap memory usage tracking
  • top util用于java进程内存使用情况跟踪

当我连续3次开始加载测试时,我观察到(使用top)java进程增加了大量已用内存:

  • 在Tomcat启动后使用~1Gb
  • 在首次测试运行后使用4.5Gb
  • 当所有测试完成后,Tomcat正在使用7Gb的RAM

此时堆大小有限,JProfiler确认 - 堆大小不超过512Mb。

这是JProfiler的截图。底部的红色数字是java进程使用的内存大小(根据top)。 enter image description here

问题是:为什么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,最后需要3.036.400 Kb - 差异超过10倍!每次我进行测试时它都会增长。同时堆大小是不变的(根据JProfiler / VisualVM)。我有什么选择才能找到导致此问题的原因?调试JVM?我试图找到任何方式来查看"在这段记忆但失败了。所以:

  • 我可以识别[heap]内存段的某些内容吗?
  • 看起来这种java行为会不会出现?

我将很感激有关此问题的任何提示。谢谢大家!

UPD#3 :使用jemalloc(感谢@ivan的想法)我得到了下一张图片: enter image description here

看起来我的问题与此处描述的几乎相同:http://www.evanjones.ca/java-native-leak-bug.html

UPD#4 :现在我发现问题与java.util.zip.Inflater / Deflater有关,这些类在我的应用程序的很多地方都使用过。但是对内存消耗的最大影响使得与删除SOAP服务的交互。我的应用程序使用JAX-WS标准的参考实现,它在负载下给出了下一个内存消耗(它在10Gb之后具有低精度):memory consumption with reference implementation 然后我使用Apache CXF实现了相同的负载测试,并给出了下一个结果:memory consumption with Apache CXF 所以你可以看到CXF使用更少的内存并且它更稳定(它没有像ref.impl一样增长)。 最后我在JDK问题跟踪器上发现了一个问题 - https://bugs.openjdk.java.net/browse/JDK-8074108 - 它再次讨论了zip库中的内存泄漏问题,但问题尚未解决。所以看起来我无法解决我的应用程序中的内存泄漏问题,只是可以做一些解决方法。

感谢大家的帮助!

3 个答案:

答案 0 :(得分:4)

我的假设是你在JProfiler中收集分配信息/调用堆栈/等,你观察到的RSS增长与JProfiler将这些数据保存在内存中有关。

您可以通过收集较少的信息来验证这是否属实(在分析开始时应该有一个屏幕,允许您例如不收集对象分配),并查看您是否观察到较小的RSS增长。在没有JProfiler的情况下运行负载测试也是一种选择。

过去我有一个similar case

答案 1 :(得分:1)

您可以使用此选项-XX:MaxDirectMemorySize=1024m重新运行测试吗?此限制的确切值无关紧要,但它显示可能“泄漏”

您是否还可以提供GC详细信息(-XX:+PrintGC)?

java.nio.ByteBuffer他们的可能原因,因为它具体完成。

更新#1

我看到类似的行为还有两个原因:java.misc.Unsafe(不太可能)和高负载的JNI调用。

如果没有测试的概况,很难理解。

更新#2

高负载的JNI调用和finalize()方法都会导致所描述的问题,因为对象没有足够的时间来完成。

下面的j.u.zip.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);

答案 2 :(得分:0)

基于Occam的剃刀:难道不是你有某处内存泄漏(即“无意识的对象保留”a'la Effective Java Item 6)?