Java处理进程的内存使用量会无限增加。

17 浏览
0 Comments

Java处理进程的内存使用量会无限增加。

前提条件:

  • 一台拥有16Gb内存的PC
  • 在Ubuntu 16.10 x64上安装了JDK 1.8.x。
  • 一款基于Spring框架的标准Web应用程序,部署在配置为以下参数的Tomcat 8.5.x上: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堆内存使用情况的JProfiler 9.x
  • top工具 用于跟踪Java进程内存使用情况

当我按顺序启动3次负载测试时,我发现(使用top)Java进程正在增加所使用的内存数量:

  • Tomcat启动后,它会使用约1Gb的内存
  • 第一次进行测试运行后,它使用4.5Gb
  • 当所有测试完成时,Tomcat将使用7Gb的内存

这整个时间堆大小都是有限制的,而JProfiler也证实了这一点-堆大小不会超过512Mb。

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

\"enter

问题是:为什么Java进程在工作时始终增加内存使用量?

谢谢!

更新#1:关于可能出现的重复问题:他们已确认此问题仅发生在Solaris上。但我使用的是Ubuntu 16.10。此外,指向的问题没有回答可以解释问题的原因。

UPD#2: 经过一段间隔之后,我现在使用 pmap 工具来制作 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

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

UPD #4: 我目前发现这个问题与 java.util.zip.Inflater/Deflater 有关,这些类在我的应用程序中的许多地方都使用了。但内存消耗最大的影响是与远程SOAP服务的交互。我的应用程序使用了 JAX-WS 标准的参考实现,并在负载下给出了下一个内存消耗(在10GB后精度较低):\"memory 然后我进行了相同的负载测试,但使用了Apache CXF 的实现,结果如下:\"memory 因此,您可以看到 CXF 使用的内存更少,更稳定(它不像参考实现一样一直增长)。 最后,我在 JDK 的问题跟踪器中找到了一个问题 - https://bugs.openjdk.java.net/browse/JDK-8074108 - 它再次涉及压缩库中的内存泄漏问题,问题尚未关闭。 所以看来我无法真正解决我应用程序中的内存泄漏问题,只能做一些解决方法。

感谢大家的帮助!

admin 更改状态以发布 2023年5月24日
0
0 Comments

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

你能提供GC详情(-XX:+printGC)吗?

java.nio.ByteBuffer是由于其特定的finalize可能是它们的一个可能原因。

更新#1

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

在没有测试概要的情况下,很难理解。

更新#2

高负载的JNI调用和finalize()方法都会导致所描述的问题,因为对象没有足够的时间完成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);

0
0 Comments

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

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

我以前也有一个类似的案例

0