服务异常跟踪

1、业务表现

服务不可用,转圈圈

2、上日志

Redisson + Netty 堆内存溢出(OOM)问题完整解决方案

你遇到的核心错误是:Java 堆内存不足,且溢出发生在 Redisson 编码发送 Redis 命令 的阶段(Netty 编码器),这是典型的大对象 / 大批量命令 / 内存配置不足导致的 OOM。

先给你最关键的结论:

这个 OOM 不是普通的内存泄漏,90% 是因为你一次性往 Redis 发送了超大数据 / 批量命令,或者 JVM 堆内存配置太小,Redisson 编码时直接撑爆了堆。

一、快速定位根因(3 个最常见原因)

结合你的日志(CommandEncoder/CommandBatchEncoder),问题来源只有这几种:

  1. 批量操作过大(最常见)使用 RBatch 批量执行几千 / 几万条 Redis 命令,Redisson 编码时会一次性加载所有命令到内存,直接溢出。
  2. 存储超大 Value往 Redis 塞超大对象 / 超大集合(比如 100MB + 的 JSON、List),序列化 + 编码时内存暴涨。
  3. JVM 堆内存默认配置太小生产环境 JVM 默认堆只有几百 MB,稍微大一点的操作就溢出。

二、立即生效的修复方案(按优先级执行)

1. 第一步:增大 JVM 堆内存(最快解决)

直接修改 Java 启动参数,这是最立竿见影的方案

# 推荐配置(根据服务器内存调整,最小给 2G 堆)
java -Xms2g -Xmx4g -jar your-app.jar

参数说明:

  • -Xms2g:初始堆内存 2GB
  • -Xmx4g:最大堆内存 4GB

服务器内存≥8G → 推荐 -Xmx4g -Xms4g;服务器内存≥16G → 推荐 -Xmx8g -Xms8g

2. 第二步:修复 Redisson 批量操作(核心问题)

如果你代码里用了 RBatch(批量命令),绝对不能一次性提交大量命令

❌ 错误写法(会 OOM):

RBatch batch = redisson.createBatch();
// 循环10万条命令,直接撑爆内存
for (int i = 0; i < 100000; i++) {
    batch.getMap("test").putAsync("key"+i, "value");
}
batch.execute();

✅ 正确写法(分批提交,每 1000 条执行一次):

// 每1000条命令提交一次,避免内存堆积
int batchSize = 1000; 
RBatch batch = redisson.createBatch();

for (int i = 0; i < 100000; i++) {
    batch.getMap("test").putAsync("key"+i, "value");
    
    // 达到阈值立即执行,清空内存
    if ((i + 1) % batchSize == 0) {
        batch.execute();
        batch = redisson.createBatch();
    }
}

// 执行剩余命令
if (batch.isActive()) {
    batch.execute();
}

3. 第三步:禁止存储超大 Value 到 Redis

❌ 不要存:

  • 10MB 以上的单个对象
  • 包含 10 万条数据的 List/Set
  • 超大 JSON 字符串

✅ 优化方案:

  1. 大对象拆分(分多个 key 存储)
  2. 大集合分页存储
  3. 使用二进制压缩(Redisson 支持 snappy 压缩)

Redisson 开启数据压缩(降低内存占用)

redisson.yml配置中添加:

threads: 16
nettyThreads: 32
codec: !<org.redisson.codec.SnappyCodec> {} # 开启压缩

4. 第四步:优化 Redisson 连接配置(防止堆积)

修改 Redisson 配置,限制命令队列大小,避免无限堆积撑爆内存:

singleServerConfig:
  address: "redis://127.0.0.1:6379"
  connectionMinimumIdleSize: 10
  connectionPoolSize: 64
  retryAttempts: 3
  retryInterval: 1000
  # 关键配置:限制每个连接的等待命令队列,防止无限堆积
  pendingQueueSize: 2000

三、高级排查:确认到底是什么占了内存

如果上面方案还没解决,你需要抓堆 dump分析:

  1. 添加 JVM 参数,自动生成内存溢出快照:shell
  2. JProfiler / Eclipse MAT 工具打开 dump 文件
  3. 直接查看:org.redisson相关对象占用的内存大小

你会直接看到:

  • 超大的CommandBatch对象
  • 超大的待发送 Redis 命令队列

四、总结:必做的 3 件事

  1. 加大 JVM 堆内存-Xms2g -Xmx4g 起步
  2. 拆分批量操作:RBatch 每 1000 条命令执行一次,不要无限累加
  3. 禁止超大 Value:拆分大对象、开启数据压缩

#牛客AI配图神器#

#面试时最害怕被问到的问题##机械人面试中的常问题##春招至今,你收到几个面试了?#
全部评论

相关推荐

04-15 01:50
门头沟学院 Java
整体面试还是不错的,但是没后续了,不知道是哪里出了问题1.实习拷打2.&nbsp;double&nbsp;处理金额会发生什么问题?通常用什么来替代&nbsp;double?3.&nbsp;字符串怎么转成字节数组?在&nbsp;Java&nbsp;中默认编码是什么?4.&nbsp;一个英文字符占多少位?中文占多少位?5.&nbsp;创建&nbsp;MySQL&nbsp;数据库的编码utf8mb3&nbsp;和&nbsp;utf8mb4&nbsp;有什么区别?6.&nbsp;有在工作中处理过&nbsp;emoji&nbsp;表情字符吗?怎么去存储表情在数据库中?7.&nbsp;字符串拼接用&nbsp;+&nbsp;号和用&nbsp;StringBuilder&nbsp;有什么区别?8.&nbsp;遍历&nbsp;List&nbsp;的过程中,根据条件删除元素的正确操作是什么?(注:不能使用&nbsp;list&nbsp;本身自带的&nbsp;remove,要用迭代器的&nbsp;remove)9.&nbsp;初始容量为&nbsp;4&nbsp;的&nbsp;ArrayList,放&nbsp;10&nbsp;个元素会发生几次扩容?10.&nbsp;假设有一个对象包含一个非静态的&nbsp;private&nbsp;方法,如何通过反射调用它?需要哪几步操作?11.&nbsp;8G&nbsp;内存的机器跑一个&nbsp;Java&nbsp;常驻进程,你会怎么分配堆内存和元空间?设置哪几个参数比较好?12.&nbsp;假设是一个需要用到&nbsp;NIO&nbsp;的&nbsp;Netty&nbsp;之类的一些框架的一个程序,出现操作系统把&nbsp;Java&nbsp;程序杀掉的情况,该从哪些方面怎么排查?(答了内存、cpu、日志三个方面)13.&nbsp;如果死前内存占用&nbsp;97%,死后降至&nbsp;15%;死前&nbsp;CPU&nbsp;占用仅&nbsp;20%-30%,这是发生了什么?(答了可能出现了循环创建等情况)14.&nbsp;Java&nbsp;循环创建会先把分配好的堆内存打满吗?请描述从开始循环创建到进程被杀死的整个流程。15.&nbsp;静态对象或变量会出现循环创建吗?16.&nbsp;在上述内存打满、CPU&nbsp;较低的场景中,会有大量的&nbsp;Full&nbsp;GC&nbsp;吗?为什么?(不会,因为&nbsp;Full&nbsp;GC&nbsp;会使&nbsp;CPU&nbsp;占用过高)17.&nbsp;操作系统判定进程资源使用不合理将其干掉,去哪看系统底层日志?
查看16道真题和解析
点赞 评论 收藏
分享
评论
1
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务