框架经常要「按 Class 缓存一点东西」。用 HashMap 会拖住类加载器,ClassValue 为什么不会?并发第一次访问会怎样?比 ConcurrentHashMap 快多少?这篇逐项验证,并用真实的类加载器检查泄漏。
1 ClassValue 基本行为: NAME.get(String.class) = info:String;再取一次 = info:String;computeValue 调用次数 = 1 NAME.get(Integer.class) = info:Integer;子类 ArrayList / List 各自计算:info:ArrayList,info:List;调用次数 = 4 remove(String.class) 之后再取:info:String;调用次数 = 5 2 8 个线程同时第一次取同一个类的值:所有线程拿到的值 [42];computeValue 实际被调用了 8 次(允许多个线程各算一次,但只有一个结果被保留) 3 computeValue 抛异常:get 把异常原样抛出:IllegalStateException;再取一次会再算一次:仍然抛出 4 computeValue 返回 null:get 返回 null;调用次数 1(null 也被缓存了)
5 按 Class 取缓存值(8 个类轮流,5000 万次取最小):ClassValue.get 2.24 ns;ConcurrentHashMap.get 3.10 ns;HashMap.get 4.74 ns;IdentityHashMap.get 4.46 ns(校验 0)
none → 加载器已被回收 classvalue → 加载器已被回收 classvalue-holds-class → 加载器已被回收 chm → 加载器仍然存活(泄漏) weak-plain → 加载器已被回收 weak-value-refs-key → 加载器仍然存活(泄漏)