先确认是Redis内存高还是JVM进程内存高:用redis-cli info memory | grep used_memory_human查Redis端用量;用jcmd pid VM.native_memory summary scale=MB看Direct+Internal是否远超-Xmx,若pmap总RSS显著高于JVM committed,则存在Lettuce堆外内存泄漏。

Spring Boot集成Redis后内存占用过高,八成不是Redis本身爆了内存,而是应用进程(JVM)被反序列化对象、堆外缓冲区或失控缓存项持续吃掉——得先分清是“Redis内存高”还是“Java进程物理内存高”,再针对性查。
怎么确认到底是Redis内存高,还是JVM进程内存高
别一上来就翻Redis配置。先看真实资源归属:
- 执行
redis-cli info memory | grep used_memory_human,看Redis服务端实际使用的内存量(比如used_memory_human:1.2G) - 在应用侧执行
jcmd pid VM.native_memory summary scale=MB,重点看Internal和Direct两栏:如果这两项加起来远超JVM堆(-Xmx),说明是Netty堆外内存没释放 - 用
pmap -x pid | awk '{sum += $3} END {print sum}'算总RSS,若比jcmd报的committed大很多,大概率有未被JVM追踪的Native内存(比如Lettuce底层的DirectByteBuffer泄漏)
为什么 getObject() 调用会把JVM堆撑爆
不是Redis占得多,是你每次调用 redisTemplate.opsForValue().get() 或 redisTemplate.getObject(),都强制把整个JSON反序列化成Java对象,而这些对象可能根本没被GC回收:
-
Jackson2JsonRedisSerializer不支持流式解析,5MB的JSON进来,直接new出一个几百MB的List<Order>实例 - 如果业务代码里有静态Map缓存了这些对象,或单例Service持有引用,GC就永远扫不到
- 更隐蔽的是:key在Redis里已过期,但你的定时任务还在反复调用
getObject("user:all", List.class)—— Redis不存了,JVM里却不断new新对象 - 检查所有
set()调用点:timeout参数是否为null?默认不设TTL就是永久key,后续所有get都是在加载“僵尸数据”
为什么Lettuce连接池会让物理内存飙升
Lettuce依赖Netty,而Netty大量使用 DirectByteBuffer,这部分内存不走JVM堆,但一样吃物理内存:
- 现象是压测时
jcmd VM.native_memory显示Direct栏持续上涨,used_memory却稳定,JVM堆也正常 - 根因常是连接池配置失衡:
spring.redis.lettuce.pool.max-active设太高(比如200),但QPS只有80,空闲连接长期占用Netty线程+DirectBuffer - 高频短连接场景下,
NettyPooledByteBufAllocator的arena回收失效,io.netty.util.internal.OutOfDirectMemoryError就是典型信号 - 临时缓解可加参数:
spring.redis.lettuce.pool.max-active=120、min-idle=2;根治建议换Jedis(需手动配JedisConnectionFactory),它不依赖Netty
怎么快速定位“不该存的数据”进了Redis
别只信代码里有没有 set(),直接查Redis里到底有什么:
- 用
redis-cli --scan --pattern "*"扫所有key,观察是否有带时间戳、UUID、用户ID的泛滥key(比如user:12345:profile:20260703) - 对可疑key,用
redis-memory-for-key -s host -p port key_name查单个key内存占用,>10KB的String或>5000个field的Hash就得警惕 - 用
rdbtools解析RDB快照生成CSV:rdb -c memory dump.rdb > dump.csv,按memory列排序,一眼揪出TOP 10大Key - 特别注意:
StringRedisTemplate误存Base64图片、PDF二进制,value看似是String,实则内部是大byte[],序列化后体积翻倍
最易被忽略的点:内存问题从来不是单一原因。常见组合是——Lettuce堆外泄漏 + 无TTL的大Key + getObject() 频繁反序列化,三者叠加,物理内存几分钟内就能从4G飙到7G以上。排查必须同步看Redis服务端指标、JVM native memory、以及应用运行时连接池和缓存行为。


















