Java分布式锁心跳线程泄漏本质是Redisson看门狗线程未回收,主因是客户端未单例管理或shutdown未调用,导致OutOfMemoryError。

Java 分布式锁客户端出现心跳线程泄漏,本质是看门狗(Watchdog)线程未被正确回收,常见于 Redisson 客户端使用不当或生命周期管理缺失。这类泄漏不会立刻报错,但会持续累积线程,最终触发 java.lang.OutOfMemoryError: unable to create new native thread。
确认是否真为心跳线程泄漏
先验证问题存在性,避免误判:
- 用
jstack -l <pid>导出线程快照,搜索关键词redisson-netty或watchdog,观察类似redisson-3-1、redisson-netty-2-4的线程数量是否随客户端反复创建/销毁而递增 - 检查线程栈中是否包含
renewExpiration、HashedWheelTimer、scheduleExpirationRenewal等调用链 - 对比多次 dump 中相同线程名的
java.lang.Thread @<addr>地址是否新增——地址不同即为新线程,说明未复用或未关闭
重点排查客户端生命周期管理
Redisson 的看门狗线程由 RedissonClient 实例持有并启动,其生命周期必须与客户端严格对齐:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
-
禁止每次加锁都 new 一个 RedissonClient:这是最常见原因。应全局单例或通过 Spring Bean 管理,确保
redissonClient.shutdown()被调用 - Spring 环境下检查
@Bean是否标注了@Scope("singleton")(默认就是),避免误配prototype - 若手动管理,务必在应用关闭钩子(如
Runtime.getRuntime().addShutdownHook())或容器销毁回调中执行redissonClient.shutdown() - 注意
shutdown()是异步的,如需同步等待,可调用shutdown(10, 2, TimeUnit.SECONDS)指定超时
检查锁对象未释放或异常中断场景
即使客户端正常关闭,若某次加锁后未 unlock,也可能导致看门狗残留:
立即学习“Java免费学习笔记(深入)”;
- 查看业务代码中所有
RLock.lock()或tryLock()是否都配对了unlock(),且unlock()放在finally块中 - 特别注意:如果业务逻辑抛出未捕获异常(如
RuntimeException),又没在finally中 unlock,该锁实例的看门狗任务可能仍在运行 - 调用
lock.isHeldByCurrentThread()判断后再 unlock,避免重复 unlock 异常干扰,但不能替代finally保障 - 不建议使用无参
lock()(阻塞式),因其无超时控制,一旦网络卡顿或 Redis 不可用,线程可能长期挂起,间接阻碍 shutdown 流程
规避自定义锁续期逻辑干扰
手动启用看门狗或覆盖默认行为易引入泄漏风险:
- 避免显式调用
lock.lock(leaseTime, unit)传入正数(如lock.lock(30, TimeUnit.SECONDS)),这会禁用看门狗;但若混用lock()和lock(leaseTime, ...),部分锁实例可能意外开启续期却未清理 - 不推荐自行基于 Netty
HashedWheelTimer或ScheduledExecutorService实现续期——Redisson 内部已封装完备,重复实现极易漏掉 cancel 逻辑 - 确认未通过反射或字节码增强方式修改
RedissonLock类的内部定时器行为

















