直接内存溢出需显式配置-xx:maxdirectmemorysize划红线,推荐设为物理内存1/4~1/3且与-xmx之和≤物理内存80%;释放依赖代码规范(如netty中.release())、避免长持引用、优先池化,并通过nmt、jmx和printdirectmemorydetails验证。

直接内存溢出(java.lang.OutOfMemoryError: Direct buffer memory)不是堆内存问题,它发生在 JVM 堆外、由操作系统直接分配的区域,无法靠 GC 自动回收全部内容。释放靠 Cleaner 机制触发,配置则必须显式干预——只设上限不管理生命周期,等于给定时炸弹加个表盘。
怎么配置上限才合理
核心是用 -XX:MaxDirectMemorySize 划清红线:
- 必须显式设置,不能依赖默认值(JVM 默认等于
-Xmx,易与堆争抢物理内存) - 推荐值为物理内存的 1/4~1/3,且
-Xmx + -XX:MaxDirectMemorySize ≤ 物理内存 × 80%(预留线程栈、Metaspace、CodeCache 等) - 示例:32GB 服务器,
-Xmx12g→ 可设-XX:MaxDirectMemorySize=8g;轻量 HTTP 服务可设=256m或=0(慎用) - 单位支持后缀:
m、g,如-XX:MaxDirectMemorySize=512m
怎么真正释放直接内存
配置只是限流,释放靠代码规范和 Cleaner 正常运转:
- Netty 场景下,每个
ByteBuf必须在业务逻辑结束时调用.release(),异常分支也要用finally保障 - 手动触发清理(紧急或大 Buffer 场景):
((DirectBuffer) buffer).cleaner().clean() - 避免长期持有引用:禁止将
DirectByteBuffer放入静态集合、缓存、单例或长生命周期对象中 - 使用池化:优先用
PooledByteBufAllocator(Netty)或 JDK 的BufferPool,减少频繁分配 - 大文件映射(
MappedByteBuffer)用完后,应主动清理或控制映射粒度,不可依赖System.gc()
怎么验证配置和释放是否生效
不验证的配置等于没配:
- 启动加
-XX:NativeMemoryTracking=summary,运行中执行jcmd <pid> VM.native_memory summary</pid>,看Direct行是否稳定不持续上涨 - 监控 JMX 指标:
java.nio:type=BufferPool,name=direct中的MemoryUsed和Count,若长期逼近上限且不回落,就是泄漏 - 压测后加
-XX:+PrintDirectMemoryDetails,JVM 退出时输出分配/释放统计,查未释放总量 - 用 Arthas 查 Netty 实例数:
vmtool --action getInstances --className io.netty.buffer.PooledByteBufAllocator,防多实例绕过单限额
特别注意框架绕过限制的情况
某些组件不走 JVM 统一管控:
- Netty 可通过
-Dio.netty.maxDirectMemory单独设限,优先级高于 JVM 参数 - 若开启
-Dio.netty.tryReflectionSetAccessible=true,可能走Unsafe分配,完全绕过MaxDirectMemorySize和 NMT 监控 - MySQL 驱动等若启用
useDirectByteBuffer=true,需评估是否关闭;MappedByteBuffer不受该参数约束,得从业务层控制 - Docker/K8s 环境中,确保 JDK ≥ 8u191 或 ≥ 10,并启用
-XX:+UseContainerSupport,再配合容器内存限制调低MaxDirectMemorySize
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











