内存涨上去不肯下来 - 未必是内存泄漏

在一个Kubernetes(K8s)集群中,部署了Prometheus和Grafana用于监控集群本身和应用的状态。

在其中一个Java应用对应的Pod级别观察到了内存上升的现象。具体而言,当该应用刚启动时,内存占用并不高。如果不发送请求给应用,内存将保持在启动时的水平上。

如果大量发送请求给应用并在短时间内持续发送,内存会迅速增加。这在一定程度上是正常的。

一旦内存增加之后,即使停止发送请求和压力,内存使用也不会下降,一直保持在高峰水平。

上面的状况是由Grafana中观察到的。

观察到的现象看起来像是内存泄漏,但实际上并不一定是内存泄漏。

原因有以下两点:

1 在K8s中运行的Prometheus默认只使用了Node Exporter

这意味着Prometheus收集的数据是从操作系统的角度来看进程的内存使用情况,而不是从Java虚拟机(JVM)进程内部观察。

如果想要从JVM内部的视角观察堆内存的使用情况,例如堆的大小和使用情况,就需要让应用容器内包含有Prometheus的jmx exporter。

2 关键是要观察堆内存的使用情况

要检查和确诊Java应用的内存泄漏,不能仅仅从操作系统的角度观察整个进程的内存使用情况,认为内存没有释放就是泄漏。这种观察方式是不准确的。

应该从JVM内部观察堆内存的使用情况,即使进行了垃圾回收(GC),堆内存仍然无法下降是一个明确的征兆。

例如,堆使用量(heap usage)基本上接近堆大小(heap size),并且堆使用量出现了频繁的小锯齿波动,这基本上表明GC在尝试清理旧的内存,但无法成功清理,这就是比较明显的迹象了。

因为JVM有时候不愿意释放从操作系统那里要来的内存。因此,仅仅根据从操作系统的角度观察内存是否增加而不下降来诊断为Java的内存泄漏是不准确的。