weakhashmap的key回收是gc驱动+惰性清理:key仅被弱引用持有时,gc可回收它并入队referencequeue,但entry物理删除需后续map访问触发expungestaleentries();value为强引用,若反向引用key会导致内存泄漏。

WeakHashMap 的 key 弱引用回收语义,不是“自动定时清理”,而是“GC 驱动 + 惰性清理”的组合机制:key 只要失去所有外部强引用,就具备被回收条件;真正从 Map 中消失,则需 GC 回收该 key 后,再经一次 map 访问触发清理。
Key 回收完全依赖 GC,不主动扫描
WeakHashMap 不会轮询或定时检查 key 是否存活。Entry 内部把 key 包装成 WeakReference,JVM 垃圾收集器在执行时,若发现某个 key 对象仅被 WeakReference 持有(即无任何强引用路径可达),就会将其回收,并将对应 WeakReference 入队到内部的 ReferenceQueue。此时 entry 仍留在哈希桶中,尚未删除。
- 没有 GC,key 就不会被回收,entry 也一直保留在表里——哪怕它对应的对象早已逻辑上“失效”
- Full GC 或老年代 GC 更可能触发弱引用回收,但 Minor GC 也可能清理部分弱引用(取决于 JVM 实现和堆布局)
- System.gc() 是提示,不保证执行,也不保证立即见效;生产环境应避免依赖它控制回收时机
清理发生在访问时,非实时移除
被 GC 回收的 key 对应的 entry,并不会在 GC 完成后立刻从 WeakHashMap 中物理删除。真正清理动作由 expungeStaleEntries() 方法完成,而该方法只在以下操作中被隐式调用:
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
- put、get、containsKey、containsValue
- size、keySet、entrySet、values
- 迭代器 next()(如 for-each 循环首次调用)
也就是说,即使 key 已被 GC 回收,只要没调用这些方法,map.size() 仍可能返回旧值,遍历仍可能看到已失效的 entry(其 getKey() 返回 null)。
Key 必须是“可被回收的对象”,常量池对象无效
WeakHashMap 的弱引用语义对某些对象完全不起作用:
- String 字面量(如 "abc")、Integer(-128~127) 等常量池对象,被 JVM 全局强引用持有,永远不会被 GC → 对应 entry 永不消失
- 静态单例、Spring Bean、全局缓存对象等长期存活实例,同样难被回收
- 安全做法是使用 new 创建的实例作 key,例如 new String("abc")、new UserSession()、new byte[1024*1024] 等
Value 是强引用,反向引用会导致泄漏
WeakHashMap 只保障 key 的弱性,value 始终为强引用。这带来两个关键约束:
- value 若持有对 key 的强引用(如匿名内部类、lambda 表达式捕获 this、监听器回调中保存 key 引用),会形成“key → value → key”闭环,使 key 无法被 GC
- value 本身不随 key 回收而释放——只有当 value 也被其他地方弃用时,才会被回收;大 value 长期滞留会拖慢内存释放
- 若需 value 也弱化,需自行封装,例如用 WeakReference
作为 value 类型
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










