2016-11-18 246 views
17

前提条件:Java进程的内存使用量不断增加无限

  • PC安装在Ubuntu 16.10 64 GB的RAM 16
  • 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运行
  • 为Java堆内存使用情况跟踪
  • top util的对Java进程的内存使用的JProfiler 9.x的负载测试跟踪

当我启动我观察到3次使用负载测试(使用top),java进程正在增加一些已用内存:

  • after Tomca吨启动它使用〜1Gb的
  • 第一测试运行它使用4.5GB
  • 当所有的测试都Tomcat正在使用的RAM

7GB的这段时间堆大小是有限的,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。他们是相当大的,但最有趣的东西是在堆的大小的第八行:测试前需要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库中的内存泄漏问题,该问题尚未结束。所以它看起来像我不能真正解决我的应用程序中的内存泄漏问题,只能做一些解决方法。

谢谢大家的帮助!

+1

可能的复制。内存泄漏?](http://stackoverflow.com/questions/36185685/java-process-memory-growing-indefinitely-memory-leak) –

+0

根据我的理解,垃圾收集器不能执行它的清理过程,由于高负载/内存问题 –

+0

@mayankagrawal堆大小不超过512Mb - GC能够释放未使用的内存。 –

回答

3

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

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

我过去有一个similar case

+0

你是对的,探险家在附加到应用程序时吃额外的内存。但是我没有使用任何profiler工具做了最后的测试,并且看到了相同的行为 - 内存使用量无限增长。我目前的假设是,问题与JAX-WS库有关(因为它是应用程序中使用最多的部分)... –

+0

@RomanProshin您是否使用带有堆外功能的缓存解决方案? (如Ehcache)。 – Ivan

+0

你也试过看看[jemalloc](http://jemalloc.net/)吗? – Ivan

0

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

+0

理解这是一个* native *内存泄漏而不是普通的Java对象泄漏是很重要的。 Java堆很好。在Java程序中很难从本机内存泄漏。 –

+0

@ChristopherSchultz确实,mea culpa。对不起,我很抱歉。 –

1

你可以用这个选项重新运行你的测试-XX:MaxDirectMemorySize=1024m?这个限制的确切值并不重要,但它显示可能的“泄漏”

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

java.nio.ByteBuffer他们由于其具体的最终确定可能的原因。

更新#1

我见过类似的行为有两个原因,另一:java.misc.Unsafe(不太可能)和高载JNI通话。

这是很难理解没有测试的配置文件。

更新#2

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

以下j.u.zip.Inflater片段:Java进程的内存无限增长的

/** 
* 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); 
+0

这就是我收集的内容:https://gist.github.com/proshin-roman/04acfa9b2a8636207ef450691e00bfd9(两个推荐选项均已启用)。内存消耗与以前的测试相同。 –

+0

@ gregory-k我不使用任何JNI调用或不安全的调用...下一次尝试将使用jemalloc - 也许它会识别应用程序中的一些问题。 –

+0

@RomanProshin,在java标准库本身中有许多JNI调用。请参阅juzip.Inflater:http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/tip/src/share/classes/java/util/zip/Inflater.java例如,使用此类用于使用ZLIB库进行解压缩。 –