如何不使用固定超时即可感知WeakHashMap的条目更新完成?
问题核心解答
你遇到的现象本质是两个异步流程的时间差导致的:
System.gc()只是向JVM发出GC执行建议,即便GC立即执行,弱可达的key被标记回收后,对应的弱引用实例需要被JVM的ReferenceHandler异步线程放入关联的引用队列,这个过程和业务线程是并行的。WeakHashMap的过期条目清理是惰性触发的,只有调用put/get/size等公共方法时,才会遍历内部的引用队列删除已回收key对应的条目,GC刚执行就调用size()时,引用还没入队,清理逻辑自然还没执行。
你示例中加Thread.sleep(0)就返回正确结果,本质是让出了CPU时间,给ReferenceHandler线程完成了引用入队的操作。
替代固定超时的优雅方案
方案1:自定义ReferenceQueue阻塞监听回收事件
WeakHashMap内部的引用队列是私有成员,无法直接访问,你可以为每个需要监听的key单独注册弱引用/虚引用到自己创建的ReferenceQueue,监听队列的入队事件就可以精准感知key被回收的时机,不需要固定超时:
WeakHashMap<Integer, Integer> map = new WeakHashMap<>(); // 自定义引用队列 ReferenceQueue<Integer> queue = new ReferenceQueue<>(); Integer i = 1000; // 把需要监听的key注册到自定义队列 WeakReference<Integer> watchRef = new WeakReference<>(i, queue); map.put(i, 1); System.out.println(map.size()); // 输出1 i = null; System.gc(); // 阻塞等待引用入队,没有固定超时,入队就立即返回 Reference<? extends Integer> receivedRef = queue.remove(); if (receivedRef == watchRef) { // 主动触发WeakHashMap的惰性清理逻辑 map.size(); System.out.println(map.size()); // 输出0 }
如果不想阻塞业务线程,也可以单独开一个守护线程监听引用队列,事件触发后走回调逻辑即可。
方案2:轻量轮询引用入队状态
如果不需要阻塞等待,也可以用轮询加CPU让出的方式,比固定长超时的资源消耗更低:
// 承接上面的代码,i置为null并触发GC后 while (!watchRef.isEnqueued()) { Thread.yield(); // 让出CPU避免空转占用过高 } map.size(); System.out.println(map.size()); // 输出0
注意:
System.gc()的执行依赖JVM配置,如果JVM启动时加了-XX:+DisableExplicitGC参数,显式调用GC会被直接忽略,上述示例的前提是显式GC允许被执行。
内容的提问来源于stack exchange,提问作者Gonen I
相关产品推荐
相关产品推荐

