编码代理与终端 · 收录于 2026-08-30 · 适用 1 工具 ✓ 实测验证

Netty 直接内存 OOM:OutOfDirectMemoryError / Direct buffer memory(ByteBuf 泄漏 + MaxDirectMemorySize 未设)

已验证 2026-08-30Netty OutOfDirectMemoryError——显式 -XX:MaxDirectMemorySize 隔离堆与直接内存并给足容器限制(止血);根治是请求体缓存加大小门槛 + 响应式链路释放兜底。

快速修复

# 止血:显式隔离堆与直接内存,容器限制给足
java -XX:MaxDirectMemorySize=1000m -XX:+UseG1GC -Xms1000m -Xmx1000m -jar gateway.jar
# K8s Deployment 内存限制 1500Mi -> 2500Mi

# 根治:请求体缓存加大小门槛(超过直接跳过缓存,记 warn),响应式链路补释放兜底
# - Filter 里 MAX_CACHE_BODY_SIZE = 1MB,超限跳过 body 缓存
# - readBody 的 flux 链补 onErrorResume / doFinally 释放
# - AccessLogFilter 同样限制聚合大小

你会遇到什么

Spring Cloud Gateway(reactor-netty)类网关突然偶发 500,日志出现:

io.netty.util.internal.OutOfDirectMemoryError: failed to allocate 16777216 byte(s) of direct memory (used: 1006632967, max: 1013645312)

频率逐渐升高,很快服务不可用、健康检查异常。堆内存(-Xmx)看起来没问题,GC 日志也正常——但直接内存已经见底(used 960MB / max 967MB)。

本文路径:快速修复(30 秒一段命令)→ 一步步排查(可跳读)→ 原理与变体(症状/原因/证据)。赶时间直接跳到「快速修复」。

为什么会这样

Netty 的 ByteBuf 默认走 PooledByteBufAllocator,即堆外直接内存。直接内存不受 GC 管理,分配/释放要自己负责,泄漏的 ByteBuf 不会被回收,只会一路涨到上限。三个叠加因素:

因素机制
-XX:MaxDirectMemorySize 未显式设置默认有效上限 = -Xmx;K8s 容器内存限制只留了 1500Mi,堆吃 1000MB 后直接内存空间紧张
Filter 缓存请求体无大小上限50MB 上传照读进直接内存;retain 出的新 buffer 释放完全依赖下游消费,异常/熔断超时 → 泄漏
响应式链路无兜底DataBufferUtils.join 中途断连或下游报错,中间态 buffer 变孤儿;AccessLogFilter 把响应体全量聚合进内存

低流量时被掩盖,并发上来或几个大请求同时进来就爆。

Netty 的 ByteBuf 默认走 PooledByteBufAllocator:内存从 JVM 堆外(direct memory)分配,不受 GC 管理——分配/释放全靠引用计数(refCnt),retain 增、release 减,漏一次 release 就永久泄漏,直到撞上 MaxDirectMemorySize。

三个叠加因素各有机制:上限未显式设置时默认 = -Xmx(K8s 容器只给 1500Mi 时,堆 1000MB 后 direct 空间所剩无几);Filter 缓存请求体无大小上限(50MB 照读 + 异常路径不 release → 泄漏);响应式链路 join 中途断连/报错 → 中间态 buffer 成孤儿。低流量时池化缓冲复用掩盖了泄漏,并发一上来就爆。

确定性推论:direct OOM 是「泄漏 + 上限」双变量——先修泄漏(retain/release 配对、异常路径 finally release),再调上限(-XX:MaxDirectMemorySize 与容器内存匹配);jcmd <pid> VM.native_memory 看 direct 分配趋势;

判断路径:堆没满却 OOM + 栈里有 Netty = direct 问题,不是 -Xmx 问题。

一步步来

1. 看报错签名确认是直接内存 — 排查

OutOfDirectMemoryError(Netty 包装)或 OutOfMemoryError: Direct buffer memory(JDK 原生),报错里带 used/max 两个数字——先对一下 max 是不是约等于 -Xmx(没设参数时的默认)。

2. 检查 JVM 参数与容器限制 — 排查

# 确认 MaxDirectMemorySize 是否显式设置(0 = 默认取 -Xmx)
java -XX:+PrintFlagsFinal -version 2>&1 | grep MaxDirectMemorySize
# 对照容器 limit:堆 + 直接内存 + 元空间 + 线程栈都要装得下
kubectl get deploy <name> -o yaml | grep -A2 resources

3. 找谁吃了直接内存 — 必做

  • 网关:Netty 收发本身就用直接内存(正常);重点查自定义 Filter:缓存请求体/响应体的 byte[]、DataBufferUtils.join、retain 后是否保证释放;
  • 开启泄漏检测(低流量窗口):-Dio.netty.leakDetectionLevel=paranoid,日志会输出泄漏点;

NaN

4. 修 — 必做

  • 请求体缓存加大小门槛(如 1MB),超限跳过缓存只记 warn;
  • 响应式链路补 doFinally/onErrorResume 保证释放;
  • 显式 -XX:MaxDirectMemorySize,按网关流量评估(堆与直接内存分离,谁也不挤谁);
  • K8s 容器内存限制重新核算(堆 + 直接内存 + 元空间 + 线程栈 + 系统开销)。

5. 验证 — 必做

压测原触发路径(大请求 + 并发),直接内存水位平稳不回涨;leak detection 无新泄漏点。

怎么确认修好了

同一触发路径下不再出现 OutOfDirectMemoryError;jcmd <pid> VM.native_memory 或监控里 direct buffer 水位稳定;健康检查不再异常。

其它环境的表现

环境说明
网关类(Spring Cloud Gateway / reactor-netty)天生重度使用直接内存,必须显式 MaxDirectMemorySize + 管好 ByteBuf 释放
普通服务默认 = -Xmx 通常够用,但大文件/大响应体聚合进内存一样会爆
未实测Spring Cloud Gateway + 自定义 Filter 的完整复现未在本机跑(无网关环境),核心机制(直接内存上限默认 = -Xmx、堆空闲时直接内存独立耗尽)已实测

症状

网关偶发 500 → 频繁:日志 io.netty.util.internal.OutOfDirectMemoryError: failed to allocate N byte(s) of direct memory (used: X, max: Y),服务不可用、健康检查异常;堆内存与 GC 日志正常。

原因

直接内存(堆外)不受 GC 管理:-XX:MaxDirectMemorySize 未设置时默认 = -Xmx;自定义 Filter 无大小上限地缓存请求/响应体且释放依赖下游正常消费,异常/熔断时 ByteBuf 泄漏,最终顶满直接内存上限。

解法

  1. 显式 -XX:MaxDirectMemorySize(堆与直接内存隔离),容器内存限制同步给足;
  2. 请求体/响应体缓存加大小门槛,超限跳过;
  3. 响应式链路补 doFinally/onErrorResume 兜底释放;
  4. 开 -Dio.netty.leakDetectionLevel=paranoid 定位泄漏点;
  5. 压测验证直接内存水位。
验证证据
  • 机制实测(2026-08-30,WSL Ubuntu,OpenJDK 17.0.18):① 未设 MaxDirectMemorySize、-Xmx64m → 分配 64MB 直接内存后报 OutOfMemoryError: Cannot reserve ... (allocated: 67108864, limit: 67108864),行为证明默认上限 = -Xmx;② -XX:MaxDirectMemorySize=64m -Xmx256m → 堆很空闲但直接内存照样 64MB OOM(直接内存独立于堆、不受 GC 管控);③ 显式 256m → 分配 200MB 成功。证据:实测复现日志(2026-08-30)。
  • 未实测:网关 Filter/ByteBuf 泄漏完整复现(无 Spring Cloud Gateway 环境);Netty 的 OutOfDirectMemoryError 与 JDK Direct buffer memory 同机制,Netty 只是自定义了异常包装。

适用工具 (1)

本问题方案已挂到下列工具的方案页——从工具页也能回到这里,双向可达。

jdk · 外部工具

来源与证据

收录 验证 ✓ 实测验证适用 1 工具
方案含本地实测或来源可复核的解法;未实测项已在文中如实标注。

素材来源:https://www.cnblogs.com/zhangs1986/p/22555832

← 编码代理与终端 问题域 | 问题货架 | 遇到其它问题?去 问题空间 提交求助卡