WeakHashMap 常被介绍成"key 没人强引用就会自动从 map 里消失",但很少有人真实验证过这句话——包括它到底是"自动"到什么程度,要不要额外调用什么方法才能看到清理结果。
上一篇:finalize() 真的能让对象复活:真实验证,但 WeakReference 不会跟着活过来
WeakHashMap 的每个 key 内部都是用 WeakReference 包起来的,GC 能回收只剩弱引用的 key——但"能回收"和"map.size() 什么时候真实反映出来"是两件事。
WeakHashMapDemo.java(核心逻辑) WeakHashMap<Object, String> map = new WeakHashMap<>(); for (int i = 0; i < n; i++) { map.put(new Object(), "v" + i); // key 没有留任何其它强引用 } System.gc(); System.gc(); Thread.sleep(500); // 接下来第一次调用 map.size() 会得到什么?
10000 个 key,没有任何代码显式调用清理方法,直接看 GC 之后第一次和第二次 size() 调用的结果。
插入 10000 个 key 后,gc 之前 map.size() = 10000 gc 之后,第一次调用 map.size() = 0 再调用一次 map.size() = 0
真实结果很干脆:GC 之后第一次调用 size() 就已经是 0,不需要额外调用任何方法先"通知" map 去清理。原因在 JDK 源码里:WeakHashMap.size() 方法内部第一步就会调用私有的 expungeStaleEntries(),把已经被 GC 加入 ReferenceQueue 的过期条目真实清理掉,再统计剩余数量——这个清理动作是惰性的(要等某次真实访问 map 才会触发),但一旦触发就是完整、及时的,不存在"先看到一半清理结果"的中间状态。
再做一组更严格的对照:10000 个里故意留 200 个强引用,GC 之后看 size() 是不是精确等于 200。
对照组插入 10000 个,其中 200 个保留强引用,gc 之前 map2.size() = 10000 gc 之后 map2.size() = 200(应该正好剩下保留强引用的那 200 个)真实测出的结果和预期精确一致:200 个被强引用数组保留住的 key 全部真实留在 map 里,剩下 9800 个真实被清理干净
每一行都是真实运行这段代码得到的结果。