Java 进程的内存无限增长,但 MemoryMXBean 报告了稳定的堆和非堆大小

2022-09-03 15:46:23

我正在与一个团队合作开发在1GB Linux目标系统上运行的Java GUI应用程序。

我们有一个问题,我们的java进程使用的内存无限增长,直到Linux最终杀死Java进程。

我们的堆记忆是健康和稳定的。(我们已经广泛分析了我们的堆)我们还使用 MemoryMXBean 来监视应用程序的非堆内存使用情况,因为我们认为问题可能就在那里。但是,我们看到的是,报告的堆大小 + 报告的非堆大小保持稳定。

下面是一个示例,说明在具有 1GB RAM 的目标系统上运行应用程序时,这些数字可能看起来如何(MemoryMXBean 报告的堆和非堆,使用 Linux 的 top 命令(驻留内存)监视 Java 进程使用的总内存):

启动时:

  • 已提交 200 MB 堆
  • 40 MB 非堆已提交
  • Java 进程使用的 320 MB

1天后:

  • 已提交 200 MB 堆
  • 40 MB 非堆已提交
  • Java 进程使用的 360 MB

2天后:

  • 已提交 200 MB 堆
  • 40 MB 非堆已提交
  • Java 进程使用的 400 MB

上面的数字只是我们系统性能的“更清晰”的表示,但它们相当准确且接近现实。如您所见,趋势很明显。运行应用程序几周后,由于系统内存不足,Linux系统开始出现问题。事情开始放缓。再过几个小时,Java进程就会被杀死。

经过几个月的分析并试图理解这一点,我们仍然不知所措。我觉得很难找到有关此问题的信息,因为大多数讨论最终都会解释堆或其他非堆内存池。(如元空间等)

我的问题如下:

  1. 如果分解它,Java进程使用的内存包括什么?(除了堆和非堆内存池)

  2. 还有哪些其他潜在的内存泄漏源?(本机代码?JVM 开销?一般来说,哪些是最有可能的罪魁祸首?

  3. 如何监视/分析此内存?堆+非堆之外的所有内容目前对我们来说都是一个黑匣子。

任何帮助将不胜感激。


答案 1

我会尝试部分回答你的问题。

在这种情况下,我试图坚持的基本策略是监视每个可用内存池,打开的文件,套接字,缓冲池,线程数的最大/已用/峰值。套接字连接/打开的文件/线程可能会泄漏,您可能会错过。

在你的情况下,看起来你真的有一个原生内存泄漏的问题,这是非常讨厌的,很难找到。

您可以尝试分析内存。查看根,找出哪些是全局引用。它可以帮助您找出哪些类可能未被收集。例如,这是一个常见问题,可能需要显式处理组件。GCJNIawt

要检查JVM内部内存使用情况(不属于堆/堆外内存)-XX:NativeMemoryTracking非常方便。它允许您跟踪线程堆栈大小,gc /编译器开销等等。关于它的最大好处是,您可以在任何时间点创建基线,然后跟踪自基线创建以来的内存差异。

# jcmd <pid> VM.native_memory baseline
# jcmd <pid> VM.native_memory summary.diff scale=MB

Total:  reserved=664624KB  -20610KB, committed=254344KB -20610KB
...

您还可以使用命令生成此报告。JMXcom.sun.management:type=DiagnosticCommand/vmNativeMemory

和。。。您可以更深入地检查 pmap -x <pid> 和/或 procfs 内容。


答案 2

我们似乎终于确定了我们所遇到问题的根本原因。这是对具体导致该问题的原因的回答,因为这对其他人有用的可能性不大。

TLDR:

该问题是由JDK中的一个错误引起的,该错误现已修复,并将使用JDK 8u152实现。链接到错误报告

整个故事:

在我第一次发布这个问题后,我们继续监视应用程序的内存性能,vsminkov建议的XX:NativeMemoryTracking极大地帮助缩小和查明内存中泄漏的区域。

我们发现,“Tread - Arenas”区域正在无限增长。由于这种泄漏是我们非常确定我们之前没有经历过的事情,我们开始使用早期版本的java进行测试,看看这是否是在某个特定时间点引入的。

在回到java 8u73之后,泄漏不存在,尽管被迫使用较旧的JDK版本并不是最佳的,但至少我们现在有办法解决这个问题。

几周后,在运行更新73时,我们注意到应用程序仍在泄漏,我们再次开始寻找罪魁祸首。我们发现问题现在位于类 - 马洛克地区。

在这一点上,我们几乎可以肯定泄漏不是我们应用程序的错,我们正在考虑联系Oracle将问题报告为潜在的错误,但后来我的一位同事偶然发现了JDK热点编译器上的这个错误报告:链接到错误报告

错误描述与我们所看到的非常相似。根据报告中所写的内容,内存泄漏自java 8发布以来一直存在,并且在对JDK 8u152的早期版本进行测试后,我们现在相当确定泄漏已修复。运行 5 天后,我们应用程序的内存占用现在似乎接近 100% 稳定。类malloc区域仍然略有增长,但现在它以每天约100 KB的速度上升(与之前的几MB相比),并且仅测试了5天,我不能肯定它最终不会稳定下来。

我不能肯定地说,但似乎我们在Class malloc和Thread竞技场增长方面遇到的问题很可能是相关的。在任何时候,这两个问题在更新152中都消失了。不幸的是,更新似乎要到2017年底才能正式发布,但到目前为止,我们对早期版本的测试似乎很有希望。