排查 堆外内存泄漏是一项复杂的工作因为堆外内存不受 JVM 垃圾回收器GC的直接管理4。通常可以按照以下系统化的步骤进行排查第一步确认泄漏现象监控物理内存使用 top 或 htop 命令观察 Java 进程的 RES常驻物理内存指标。如果 RES 持续增长且远超 JVM 堆内存设置-Xmx与元空间-XX:MaxMetaspaceSize之和这通常是堆外内存泄漏的强烈信号1。查看报错日志注意日志中是否出现 OutOfDirectMemoryErrorNetty 相关或 OutOfMemoryError: Direct buffer memoryNIO 相关等异常2。第二步定位泄漏区域使用 NMT 工具JVM 自带的 Native Memory Tracking (NMT) 是排查堆外内存的核心工具但必须在应用启动时添加参数才能生效6。开启 NMT在 JVM 启动参数中添加 -XX:NativeMemoryTrackingdetail生产环境建议使用 summary 以降低性能开销1。设定基线应用启动并稳定运行后执行 jcmd VM.native_memory baseline 记录当前内存状态5。对比差异运行一段时间后执行 jcmd VM.native_memory summary.diff scaleMB 对比内存变化。重点关注 Direct Buffer、Internal 或 Thread 区域是否出现异常增长1。第三步深入追踪与代码定位如果确认是特定区域如 Direct Buffer泄漏可以通过以下方式进一步定位分析内存映射使用 pmap -x | sort -rn -k3 | head -30 查看进程占用的内存段找出占用最大且持续增长的匿名内存块anon2。线程级溯源执行 pstack 获取线程堆栈搜索持有 java.nio.DirectByteBuffer 或 io.netty.buffer.PooledUnsafeDirectByteBuf 的线程栈结合 NMT 的 detail 信息定位到具体分配内存的线程4。框架专项排查如 Netty如果是 Netty 导致的泄漏可在启动参数中加入 -Dio.netty.leakDetection.levelPARANOIDNetty 会在日志中记录未正确释放的 ByteBuf 的创建堆栈1。第四步代码审查与修复堆外内存泄漏通常由以下代码问题引起需重点审查未正确释放资源如 NIO 的 ByteBuffer.allocateDirect() 未调用 cleaner 或显式 free()Netty 中在 ChannelHandler 处理完 ByteBuf 后未调用 release() 或未传递给下一个 Handler1。资源未关闭FileChannel.map() 后未调用 unmap()或未使用 try-with-resources 正确关闭 AsynchronousSocketChannel 等流对象4。JNI 本地内存泄漏如果调用了 Native 方法需检查 C/C 代码中是否正确释放了分配的本地内存3。最佳实践建议在代码中尽量使用 try-with-resources 语句或在 finally 块中确保调用 ReferenceCountUtil.release() 等工具类来安全释放堆外资源
堆外内存泄漏排查
排查 堆外内存泄漏是一项复杂的工作因为堆外内存不受 JVM 垃圾回收器GC的直接管理4。通常可以按照以下系统化的步骤进行排查第一步确认泄漏现象监控物理内存使用 top 或 htop 命令观察 Java 进程的 RES常驻物理内存指标。如果 RES 持续增长且远超 JVM 堆内存设置-Xmx与元空间-XX:MaxMetaspaceSize之和这通常是堆外内存泄漏的强烈信号1。查看报错日志注意日志中是否出现 OutOfDirectMemoryErrorNetty 相关或 OutOfMemoryError: Direct buffer memoryNIO 相关等异常2。第二步定位泄漏区域使用 NMT 工具JVM 自带的 Native Memory Tracking (NMT) 是排查堆外内存的核心工具但必须在应用启动时添加参数才能生效6。开启 NMT在 JVM 启动参数中添加 -XX:NativeMemoryTrackingdetail生产环境建议使用 summary 以降低性能开销1。设定基线应用启动并稳定运行后执行 jcmd VM.native_memory baseline 记录当前内存状态5。对比差异运行一段时间后执行 jcmd VM.native_memory summary.diff scaleMB 对比内存变化。重点关注 Direct Buffer、Internal 或 Thread 区域是否出现异常增长1。第三步深入追踪与代码定位如果确认是特定区域如 Direct Buffer泄漏可以通过以下方式进一步定位分析内存映射使用 pmap -x | sort -rn -k3 | head -30 查看进程占用的内存段找出占用最大且持续增长的匿名内存块anon2。线程级溯源执行 pstack 获取线程堆栈搜索持有 java.nio.DirectByteBuffer 或 io.netty.buffer.PooledUnsafeDirectByteBuf 的线程栈结合 NMT 的 detail 信息定位到具体分配内存的线程4。框架专项排查如 Netty如果是 Netty 导致的泄漏可在启动参数中加入 -Dio.netty.leakDetection.levelPARANOIDNetty 会在日志中记录未正确释放的 ByteBuf 的创建堆栈1。第四步代码审查与修复堆外内存泄漏通常由以下代码问题引起需重点审查未正确释放资源如 NIO 的 ByteBuffer.allocateDirect() 未调用 cleaner 或显式 free()Netty 中在 ChannelHandler 处理完 ByteBuf 后未调用 release() 或未传递给下一个 Handler1。资源未关闭FileChannel.map() 后未调用 unmap()或未使用 try-with-resources 正确关闭 AsynchronousSocketChannel 等流对象4。JNI 本地内存泄漏如果调用了 Native 方法需检查 C/C 代码中是否正确释放了分配的本地内存3。最佳实践建议在代码中尽量使用 try-with-resources 语句或在 finally 块中确保调用 ReferenceCountUtil.release() 等工具类来安全释放堆外资源