Java 进程内存使用量持续无限增加

2022-09-01 23:52:33

前提 条件:

  • 具有 16 Gb 内存的电脑
  • JDK 1.8.x 安装在 Ubuntu 16.10 x64 上。
  • 一个标准的基于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用于 java 进程内存使用情况跟踪的 util

当我按顺序开始负载测试3次时,我观察到(使用)java进程正在增加已用内存的数量:top

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

所有这段时间的堆大小是有限的,JProfiler确认 - 堆大小不超过512Mb。

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

问题是:为什么java进程在工作时一直在增加内存使用量?

谢谢!

UPD#1:关于可能的重复:他们但我使用Ubuntu 16.10。同样,尖锐的问题也没有可以解释问题原因的答案。have confirmed that this only happens on Solaris.

UPD#2:我不得不在停顿一下后回到这个问题。现在我使用util来转储进程使用的内存。我有三个转储:在测试运行之前,在第一次测试执行之后以及一些N个测试执行之后。测试它们会为应用程序产生大量流量。所有转储都在这里:https://gist.github.com/proshin-roman/752cea2dc25cde64b30514ed9ed9bbd0。它们非常大,但最有趣的事情是在第8行,堆的大小:它需要在测试之前,最后 - 超过10倍的差异!而且每次我运行测试时,它都在增长。同时,堆大小是恒定的(根据JProfiler/VisualVM)。我必须通过哪些选项来查找此问题的原因?调试 JVM?我试图找到任何方法来“查看”这段记忆,但失败了。所以:pmapjava282.272 Kb3.036.400 Kb

  • 我能以某种方式识别内存段的内容吗?[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

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

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

我过去也有过类似的情况


答案 2

是否可以使用此选项重新运行测试?此限制的确切值无关紧要,但它显示了可能的“泄漏”。-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);