先把当时的情况摆出来。一个网关服务用 Netty 做长连接,跑一天后接口开始超时,日志里是 java.lang.OutOfMemoryError: Direct buffer memory,堆内存曲线却一直很平,-Xmx 还有富余。

真正麻烦的地方在于,几个原因的现象几乎一样。Netty 的堆外缓冲区没释放,ByteBuf 泄漏积累到上限。也可能是MaxDirectMemorySize 没显式设置,默认值按堆大小算,实际分配被 NIO 限额卡住。

另外,有代码手动 allocateDirect 但没走池化。少数情况下是连接关闭时没有 release,引用计数一直不归零。

第一件事是把堆外内存的实际用量打开监控

jcmd 1 VM.native_memory summary

接着看直接缓冲区的使用情况

jcmd 1 VM.info | grep -i direct

显式设置堆外上限并开启泄漏检测

grep -nE 'MaxDirectMemorySize|LEAK' /opt/app/bin/start.sh

剩下的就是用 pmap 对比进程的真实占用

pmap -x 1234 | tail -5

别只看报错消失,验证要多做一步。运行一天后接口不再超时,Direct buffer 的使用量在 MaxDirectMemorySize 的六成以内,进程 RSS 与堆内存的差值稳定。

补一句。堆外内存不在 GC 的视野里,只能靠泄漏检测和连接关闭时的释放保证;Netty 要开 ResourceLeakDetector 才能提前发现。线上能稳定复现的问题都不算难,难的是只在大流量下才出现的那种。

作者 9823189