ConcurrentHashMap 不会因“死循环扩容计算”导致内存泄漏,其扩容机制安全;所谓泄漏多因初始化容量过大、无界缓存滥用、强引用未释放或外部对象泄漏所致。

ConcurrentHashMap 本身不会因“死循环扩容计算”直接导致内存泄漏,但高并发场景下若使用不当,可能引发 隐性内存堆积 或 误判为泄漏的 OOM 现象。所谓“死循环扩容计算”其实是对 JDK 1.7 中 HashMap 的误解——ConcurrentHashMap 从不采用头插法、也不存在链表翻转成环的问题;它在 JDK 1.7 是分段锁(Segment),JDK 1.8 起改用 CAS + synchronized + 红黑树,扩容是多线程协作推进的,不会卡死或无限循环。
先确认是不是真泄漏:区分 OOM 类型和堆内行为
很多所谓“ConcurrentHashMap 扩容导致泄漏”,实际是以下情况之一:
- 初始化容量设得过大(如
new ConcurrentHashMap(2000000000)),触发内部 segment 或 Node 数组预分配,直接耗尽堆内存 —— 这是初始化失败,不是泄漏 - 缓存未设上限且无淘汰策略(如长期 put HQL 字符串进 ConcurrentHashMap),对象持续堆积 —— 这是业务逻辑导致的内存占用增长,本质是缓存滥用
- ConcurrentHashMap 被静态持有,且 key/value 持有外部强引用(如 value 是未关闭的 Connection 或大 byte[]),导致关联对象无法回收 —— 这才是真正的内存泄漏路径
重点排查:ConcurrentHashMap 是否成了“缓存黑洞”
生产中最多见的“伪泄漏”,是把它当无界缓存用。例如:
- HQL/SQL 字符串作为 key 存入,参数组合爆炸式增长(如
where a in (?, ?, ?...)+ 不同长度) - value 是未序列化的 VO、含 IO 资源句柄、或嵌套了 ThreadLocalMap 引用
- 没有设置最大容量、过期时间、LRU 清理逻辑
此时 MAT 分析会看到:ConcurrentHashMap$Node 占用 Retained Heap 极高,且每个 Node.value 指向大量重复或不可回收对象。这不是扩容机制问题,而是设计缺失。
检查扩容是否异常频繁(间接反映负载失衡)
虽然扩容本身安全,但高频扩容会加剧 GC 压力、拖慢吞吐,容易被误认为“泄漏征兆”:
- 观察 GC 日志中是否伴随大量
ConcurrentHashMap.resize相关堆栈(可通过 Arthas trace) - 用 JFR 或 VisualVM 查看
java.util.concurrent.ConcurrentHashMap#transfer方法调用频次 - 如果平均每秒 resize 超过 3–5 次,说明初始容量严重不足或写入速率远超预期,应调整
initialCapacity和concurrencyLevel(JDK 1.7)或关注负载分布
验证是否被其他泄漏源污染
ConcurrentHashMap 常作为“容器载体”,真正泄漏点往往在其外部:
- ThreadLocal 未 remove:value 是 ThreadLocal 对象,而该 ThreadLocal 持有大对象,线程池复用后长期驻留
- 监听器/回调注册未注销:value 是匿名内部类,隐式持有外部 Activity/Service 引用(Android 场景)或 Spring Bean
- 静态 Map + 非 static 内部类 key:key 是非静态内部类实例,导致整个外部类实例无法回收
这类问题在 MAT 的 “Path to GC Roots” 中清晰可见:ConcurrentHashMap 的某个 Node.value → 某个 Listener → 外部 Activity → Context → 大量 View 对象。

















