前言
Java进程快照的形成
- 使用 -XX:HeapDumpOnOutOfMemoryError
在应用程序启动的加入-XX:HeapDumpOnOutOfMemoryError 会使得程序员会崩溃的时候形成java_pidXXXX.hprof文件。我们可用通过eclipse 提供的mat工具进行分析 从而找到问题所在。 - 使用jmap 生成快照
在使用jmap生成快照方式,jmap -dump:format=b,file=1.hprof 6876 【6876 是PID 上面命令会生成1.hprof 的文件】 - 使用eclipse mat 工具分析 快照 http://www.eclipse.org/mat/
背景
测试环境一个新的服务每一周左右就发触发内存溢出问题。
核心报错信息如下:
java.lang.OutOfMemoryError: Java heap space
初步排查结果
1.使用redisson的延迟队列,并且用Queue.take阻塞消费
2.大概一周左右,会发生OOM
3.通过内存快照,发现是redisson 的CommandData野蛮增长 并且里面 ,都是ping的心跳指令
深入分析结果
新版本修改了org.redisson.client.handler.PingConnectionHandler这个类
旧版会导致:org.redisson.client.handler.CommandsQueue 类里边的Queue<QueueCommandHolder> queue 内存溢出,Queue<QueueCommandHolder> queue这里边放的全是PING
前通过升级 redisson->3.12.5+的版本,解决了此问题,通过分析,最终原因如下
1. Queue的take会导致当前的连接被长时间阻塞住,会导致当前连接的探活会一直按照定时任务新建CommandData,但是由于没法通信 导致对象一直在队列中排队,无限增长,导致最终OOM,只有ping发出去了 那个里面的command对象会被清理掉,等到下次连接复用的时候再new Command 这样才不会OOM
2. 小于3.12.5的redisson是不会判断当前连接是否已经持有CommandData对象会无限新建,新版本修复了这个问题
这里面需要注意的是Queue的take阻塞慎用,大家尽量用poll(timeout)的方式,一方面规避上述问题,一方面做优雅停服的话 take是做不到的
官网修正这个问题时修复了该问题:https://github.com/redisson/redisson/issues/2647
另外还发现另外一个内存泄漏问题:https://github.com/redisson/redisson/issues/2362
如果用了rblockqueue.take 这个问题在3.12.5以前版本必现
新版本核心变更PingConnectionHandler :
redisson队列原理分析
redisson的延迟原理
1. 先写入一个延迟队列(zadd)redisson是通过eval脚本写上去的
2. 延迟队列到期后 会在直接导入到目标的 blockqueue上去 目标在redis里面就是个list的数据结构
3.我们消费端通过redissonClient.getBlockingQueue(queueName) 消费
这个在redis执行的命令实际上是 BLPOP 这个质量的官方解释是
take实际转成底层TCP命令就是无限等,但这个情况只是阻塞当前的连接,对于并发的连接不会有阻塞发生,oom出问题也就是当前几个消费的连接无限new导致的,因为对于jvm是两个独立线程 但是对于redis是同一个client或者同一个connection.