classvalue 是 java 7 引入的按 class 关联缓存值的机制,因内部弱引用清理延迟或残留条目可能持有已卸载 classloader,导致类加载器泄露和 metaspace oom。

ClassValue 是什么,为什么它会引发类加载器泄露
ClassValue 是 Java 7 引入的轻量级线程局部缓存机制,用于为每个 Class 对象关联一个计算值。它本身不持有 Class 的强引用,但它的内部实现依赖于 ClassLoader 的生命周期管理。
问题出在:如果一个 ClassValue 实例被长期持有(比如静态字段),而它曾被某个已卸载的 ClassLoader 下的类触发过 get(),那么该 ClassLoader 可能因 ClassValue 内部的弱引用清理延迟或残留条目无法及时回收,导致整个类加载器及其加载的所有类无法被 GC —— 这就是典型的类加载器泄露。
常见错误现象:
- 应用重启后老是报
OutOfMemoryError: Metaspace - 使用 JVisualVM 或 Eclipse MAT 分析堆转储时,发现大量
ClassLoader实例未被回收,且被ClassValue$Entry或ClassValue$Identity持有 - 在 OSGi、Spring Boot DevTools、Tomcat 热部署等动态类加载场景中频繁发生
如何安全使用 ClassValue:避免静态持有 + 及时 remove
ClassValue 本身不是问题,滥用模式才是根源。关键在于控制其生命周期与使用范围。
立即学习“Java免费学习笔记(深入)”;
使用场景限制:
- 不要在公共静态字段中声明
ClassValue实例(如public static final ClassValue<string> CACHE = new ClassValue() { ... };</string>) - 若必须全局共享,确保该
ClassValue只服务于系统级、与应用类加载器无关的类(如String.class、Object.class) - 在模块化或插件化环境中,应将
ClassValue实例绑定到具体插件/模块的生命周期内(例如作为模块私有字段)
实操建议:
- 用完即弃:在确定某类不再需要缓存值时,调用
remove(Class)主动清理(注意:这不是自动的) - 配合
WeakReference包装外部持有者,防止反向强引用链 - 若业务逻辑允许,优先考虑
ConcurrentHashMap<class>, V></class>+computeIfAbsent,虽然少了自动清理,但行为更可控
ClassValue.get() 抛出异常时,会不会影响内部状态
不会。调用 get() 时若 computeValue() 方法抛出异常,ClassValue 不会缓存该异常结果,也不会留下脏状态。
但要注意:
- 异常本身不会中断后续对同一
Class的get()调用,下次仍会重新执行computeValue() - 如果
computeValue()中有副作用(如写日志、发请求、修改静态变量),重复抛异常可能导致意外行为 - 某些 JDK 版本(如早期 8u)在并发高负载下,异常可能暴露
ClassValue内部锁竞争问题,表现为偶发的IllegalMonitorStateException,升级到 8u292+ 或 11+ 可规避
示例:
class MyValue extends ClassValue<String> {
protected String computeValue(Class<?> type) {
if (type == Unsafe.class) throw new RuntimeException("blocked");
return type.getSimpleName();
}
}
// 多次 get(Unsafe.class) 都会抛异常,但不会污染其他 class 的缓存










