Spring Boot + Lettuce 堆外内存溢出:OutOfDirectMemoryError 复现与分析

发布时间:2026/10/4 5:31:17

Spring Boot + Lettuce 堆外内存溢出:OutOfDirectMemoryError 复现与分析 一、问题现象异常信息io.netty.util.internal.OutOfDirectMemoryError: failed to allocate 2096896 byte(s) of direct memory (used: 7406854, max: 8388608)业务影响Redis 操作全部失败接口返回 500应用可能崩溃二、问题本质Netty 向操作系统申请的堆外内存超过了-XX:MaxDirectMemorySize的限制。关键认知-Xmx管的是堆内存管不了堆外内存堆外内存由-XX:MaxDirectMemorySize独立控制Netty 的ByteBuf默认分配在堆外三、复现环境组件版本Spring Boot2.2.5.RELEASEJDK8Lettuce5.2.2Netty4.1.45JVM 参数-Xms256m -Xmx256m -XX:MaxDirectMemorySize8mJMeter压测配置接口/redis/big大量数据set/get线程数200循环次数不限制四、复现步骤建 Spring Boot 2.2.5 项目引入spring-boot-starter-data-redis关闭连接共享share-native-connection: false写一个循环操作 Redis 的接口启动参数加-XX:MaxDirectMemorySize8mJMeter 200 并发压测五、异常堆栈关键路径io.netty.util.internal.OutOfDirectMemoryError at io.netty.util.internal.PlatformDependent.incrementMemoryCounter at io.netty.buffer.UnpooledUnsafeNoCleanerDirectByteBuf.reallocateDirect at io.netty.buffer.AbstractByteBuf.ensureWritable at io.lettuce.core.protocol.CommandArgs$ByteBufferArgument.writeByteBuf at io.lettuce.core.protocol.CommandEncoder.encode at io.netty.handler.codec.MessageToByteEncoder.write关键点Lettuce 在编码 Redis 命令时通过 Netty 向堆外申请 ByteBuf超过上限崩溃。六、原因分析6.1 完整因果链高并发请求 → 每个请求操作 Redis → Lettuce 编码命令编码结果是一个 ByteBuf → ByteBuf 分配在堆外内存 → 所有命令进入同一个 EventLoop 队列共享连接模式 → 队列无长度上限堆积不断增长 → 每条排队命令都持有一份堆外 ByteBuf → 堆外内存占用持续增长 → 超过 MaxDirectMemorySize → 抛 OutOfDirectMemoryError6.2 三个关键事实事实一-Xmx管不住堆外内存堆内存堆外内存上限参数-Xmx-XX:MaxDirectMemorySize回收机制Young GC / Full GCCleaner 或 Full GC 触发溢出异常Java heap spaceDirect buffer memory/OutOfDirectMemoryError是否独立独立独立事实二Lettuce 默认共享连接同一个 EventLoop 队列Lettuce 底层用 Netty 的Channel。Netty 的Channel是线程安全的多个线程可以并发调用channel.write(command)Netty 内部把这些写操作封装成任务投递到 Channel 绑定的EventLoop 线程的队列里EventLoop单线程串行执行队列里的任务最终按顺序写入 socket所以默认情况下所有线程共用同一个 SocketChannel同一条 TCP 连接。事实三排队的命令都占着堆外 ByteBuf每条 Redis 命令在写入 socket 之前都要先编码成ByteBuf。这个ByteBuf分配在堆外。命令在 EventLoop 队列里排队时已经编码好的 ByteBuf 仍然占着堆外内存。队列越长堆外占用越高。6.3 为什么共享连接模式容易爆100 线程 → 【无闸门】→ 1 个 EventLoop 队列 → 队列长度无上限 → 每条占一份堆外 ByteBuf → 堆外暴涨 → OOM核心问题入口没有并发闸门队列没有长度限制堆外占用与队列长度成正比没有上限。6.4 连接池如何解决背压思想100 线程 → 【池大小8 的闸门】→ 8 个连接 ↓ 借不到连接 → 线程在这里等待不占堆外内存 ↓ 等待超时 → 抛异常 ↓ 借到连接 → 进入该连接的 EventLoop 队列 ↓ 堆外占用受 8 个连接上限约束关键点连接池限制的不是队列长度而是活跃连接数没抢到连接的线程在连接池层面等待不进入 Netty 队列等待只占一个线程不占堆外内存Netty 本身不做等待——分配失败时直接抛OutOfDirectMemoryError本质连接池用等待软约束、可恢复替换了内存堆积硬约束、不可恢复。这就是背压Backpressure。6.5 三层内存保护模型连接池只能管住入口层完整的内存安全靠三层层级措施解决什么入口层连接池限制活跃连接数防止并发把堆外占满单连接层命令队列长度、pipeline 批量大小防止单连接内队列堆积单操作层限制 value 大小防止单个命令本身就超上限连接池解决的是并发峰值堆积不是绝对内存不溢出。如果单个操作需要的内存超过整个堆外上限如MaxDirectMemorySize8m却要写 10MB 的 value任何框架都救不了只能调大上限或拆小数据。七、版本差异版本组合能否复现原因Spring Boot 2.2.5 Lettuce 5.2.2✅ 能无连接池 旧版 Netty 分配策略Spring Boot 2.7.0 Lettuce 6.1.8❌ 不能连接池 新版 Netty 优化7.1 新版为什么不会炸变化一Lettuce 6.x 默认启用连接池形成并发闸门连接数有上限默认 8共享连接模式被削弱等待发生在连接池层堆外占用 连接数 × 单连接缓冲总量受限变化二Netty 池化分配器更成熟缓冲区可复用PooledByteBufAllocator从本地池取 ByteBuf用完归还不需要频繁向操作系统申请/释放堆外内存单连接的堆外占用稳定不随并发线性增长7.2 深层对比维度旧版会炸新版不会炸并发闸门无共享连接连接池上限队列堆积位置Netty 队列占堆外连接池等待队列占线程堆外占用上限无上限连接数 × 单连接缓冲Netty 分配策略较激进池化复用更克制后果OOM不可恢复超时可重试/降级八、解决方案方案说明升级 Spring Boot2.7 默认 Lettuce 6.x问题自动消失调大堆外内存-XX:MaxDirectMemorySize256m或更大换 JedisJedis 用连接池不走 Netty 堆外限制并发降低连接数减少堆外申请补充说明调大MaxDirectMemorySize是治标不治本因为堆外占用如果与并发线性相关迟早会再爆。首选升级版本其次是引入连接池最后才是调大上限。九、排查工具NMTjcmd pid VM.native_memory summary看堆外使用启动参数-XX:NativeMemoryTrackingsummarypmappmap -x pid看进程内存分布十、仓库地址https://gitee.com/shun-chun-li/springboot-lettuce-direct-memory-oom
延伸阅读

更多相关文章

2026/10/4 5:31:17

轻量自托管AI网关GPT-Load 2.0:统一管理API Key与订阅账号实战

这几年做 AI 应用集成的朋友,应该都有过类似的体验:业务代码没写几行,先被一堆 Key 的配置搞得怀疑人生。我在同时维护十几把 API Key、两三个团队订阅账号之后,终于花时间把这一摊事彻底理清了——核心方案就是自己部署一个轻量自…

2026/10/4 5:26:17

STM32重制美的R05D空调遥控器:协议解析与高精度红外实现

1. 项目概述:为什么一个空调遥控器值得用STM32重做?你拆过家里那台美的空调的原装遥控器吗?我拆过三台不同年份的R05D系列,发现里面清一色是掩膜ROM的8位单片机,晶振精度1%,红外载波频率标称38kHz但实测在3…

2026/10/4 6:16:19

K210人脸检测与识别实战:KPU底层调优与边缘部署全链路

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 6:16:19

从0到1搭建AI内容生产流水线:架构设计与工程实践

1. 先搞清楚“AI内容生产流水线”到底在说什么“从0到1搭建AI内容生产流水线”这个说法,最近一年在技术社区里出现的频率越来越高。很多人第一次听到会以为就是“用AI写文章”,但真正动手做过的人都知道,这两件事之间的差距,大概相…

2026/10/4 6:16:19

建筑学AI开题报告写作指南:从场地分析到技术路线一次讲清

建筑学专业的开题报告比一般专业更"重":既要文字论述,又要设计前期分析图,设计类课题还要附场地调研。很多同学图做得漂亮,文字部分却逻辑混乱,开题答辩被评委连环追问"你的研究问题到底是什么"&a…

2026/10/4 6:16:19

基于Python+爬虫的旅游景点数据分析与可视化平台设计与实现

选题背景与意义 随着互联网技术的迅猛发展和智能设备的普及,旅游行业正经历着深刻的数字化转型。越来越多的游客在出行前依赖网络平台获取景点信息、评价反馈及行程规划建议,这使得在线旅游数据呈现出爆炸式增长。与此同时,各大旅游网站如携程…

2026/10/4 6:16:19

试了五六款论文工具后,我为什么长期留在汇写?

我属于那种"工具控",写毕业论文这大半年,前后试过的论文辅助网站不下五六款。有的主打AI写作,有的主打降重,有的主打查重,还有的就是一个空壳壳,点进去全是诱导充值。踩过的坑多了,我…

2026/10/4 6:11:19

公交数据中心云平台建设:从方案书到落地实践

简介:这份《公交数据中心云平台建设方案书》是面向智慧城市与公交企业信息化规划的技术文档,适合交通管理部门、公交集团信息化人员、云计算/大数据项目架构师参考,用于解决公交运营数据采集、处理、分析及智能决策等建设问题。全包共1个doc文…

2026/10/4 0:01:02

Jev+Agent接管浏览器:browser-use实战与jev-ultrafast性能优化

1. 从“Jev”说起:为什么我要把Agent接进浏览器“Jev”这个词最近在圈子里出现的频率越来越高,很多人第一次听到会以为是某个新模型的名字,其实它更像是一种思路——把Jev模型的能力当作底座,通过Agent的方式去接管浏览器&#xf…

2026/10/4 0:01:02

多智能体集群实战:DeepAgents编排、MCP与A2A协议及Skills体系

1. 从"单兵作战"到"集群协同":多智能体编排到底在解决什么问题如果你最近在折腾 Agent 相关的东西,大概率会有一种感觉:单个 Agent 能做的事情,其实很快就摸到天花板了。你给它一个提示词,挂几个工…

2026/10/4 1:01:05

无源低通滤波器设计实战:从RC到LC,手把手教你避开那些坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 0:01:02

Jev+Agent接管浏览器:browser-use实战与jev-ultrafast性能优化

1. 从“Jev”说起:为什么我要把Agent接进浏览器“Jev”这个词最近在圈子里出现的频率越来越高,很多人第一次听到会以为是某个新模型的名字,其实它更像是一种思路——把Jev模型的能力当作底座,通过Agent的方式去接管浏览器&#xf…

2026/10/4 0:01:02

多智能体集群实战:DeepAgents编排、MCP与A2A协议及Skills体系

1. 从"单兵作战"到"集群协同":多智能体编排到底在解决什么问题如果你最近在折腾 Agent 相关的东西,大概率会有一种感觉:单个 Agent 能做的事情,其实很快就摸到天花板了。你给它一个提示词,挂几个工…

2026/10/4 1:01:05

无源低通滤波器设计实战:从RC到LC,手把手教你避开那些坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑