内存问题:如何识别 Java 内存泄漏

一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / Java 学习路线 / 一对一提问 / 学习打卡/ 赠书活动

目前,正在 星球 内带小伙伴们做第一个项目:全栈前后端分离博客项目,采用技术栈 Spring Boot + Mybatis Plus + Vue 3.x + Vite 4手把手,前端 + 后端全栈开发,从 0 到 1 讲解每个功能点开发步骤,1v1 答疑,陪伴式直到项目上线,目前已更新了 204 小节,累计 32w+ 字,讲解图:1416 张,还在持续爆肝中,后续还会上新更多项目,目标是将 Java 领域典型的项目都整上,如秒杀系统、在线商城、IM 即时通讯、权限管理等等,已有 870+ 小伙伴加入,欢迎点击围观

每个 Java 开发人员都知道这个故事:Java 利用垃圾收集的力量自动保持应用程序内存清洁和健康。不过,我会告诉你,这只是故事的一半。虽然有用,但在后台自动执行的垃圾收集并不是保护您的应用程序免受内存泄漏的可靠方法。

工作中的 Java 垃圾收集器:

当您遇到以下消息时,您就知道您的应用程序遇到了麻烦:

java.lang.OutOfMemoryError: Java heap space

如果问题确实是分配的内存太少,您可以按如下方式增加应用程序的可用内存:

java -Xms<initial heap size> -Xmx<maximum heap size>

但是,如果您的问题是内存泄漏,那么这种方法只会转移问题。

识别内存泄漏

您甚至应该在将应用程序投入生产之前检查其是否存在内存泄漏。作为初始指标,请检查垃圾收集指标。

内存泄漏

如果内存使用在 GC 后恢复到相同水平,则一切正常。但是,如果内存不断上升,就会出现问题。上面的屏幕截图显示了一个引发的内存泄漏。代码取自 github.com 上的这个 要点 。虽然在现实中,内存消耗不会像这个例子中那样线性上升(Old Gen 是感兴趣的图表),但它清楚地表明 GC 挂起时间或 Eden 和 Survivor 空间使用不一定充当内存泄漏的指标。

解决问题

您有几个选项可以确定此类内存问题的原因(例如,JVisualVM 和 jStat)。这些工具随 JDK 一起提供,因此您可以随时投入使用并开始研究。除了识别一些经常使用的内部 Java 类之外,您还可以识别一些您自己的类。

优化和调整

您可能通常不太关心内存设置的配置方式——至少只要垃圾回收不影响性能。这可能是个问题,因为内存问题不仅仅表现为内存不足错误和内存泄漏。即将到来的内存问题可能会以高垃圾收集时间的形式表现出来(但并非总是如此)。

垃圾收集最多占用可用 CPU 使用率的 16%。

堆大小设置为小

堆大小设置得太低会导致 GC 活动增加,因为 GC 必须更频繁地执行。正如您可能想象的那样,增加 GC 使用率会导致 CPU 使用率增加——因为 JVM 在 GC 期间往往会冻结——通常性能不佳。 GC 往往很短,但很频繁。

堆大小设置为大

堆大小设置过大表现为 GC 时间过长。 GC 不会经常执行,但一旦触发,它会在相当长的时间内停止您的 VM。

现在考虑内存泄漏如何导致这种情况:内存泄漏结合了堆大小过小的问题和堆大小过大的问题:GC 被频繁触发 并且 在 JVM 最终崩溃之前需要很长时间内存不足错误。

气相色谱版本

自 Java 6 以来,垃圾收集器发生了很大变化。Java 7 引入了 G1GC 作为标准 CMS GC(并发标记和清除)的替代方案,后者将成为 Java 9 的默认设置。Java 8 放弃了 PermGen 空间。以前存储在那里的数据现在存储在本机内存或堆栈中。

虽然 Ruxit 会自动考虑这些差异,但了解这些细微差异对您来说是个好主意:

最后的想法

除了显示 JVM 统计信息之外,Ruxit 确实很出色,因为它将 JVM 指标与其他关键指标相关联。没有其他工具可以如此轻松地将 GC 暂停时间与 CPU 使用率、网络流量中断等进行比较。这使得跳出框框思考变得容易 — 异常的 JVM 行为可能是 GC 行为不当的结果,但情况并非总是如此。