核心是缓存注解方法句柄而非注解实例,用concurrenthashmap存储如"com.example.myannotation#value()"键,首次通过methodhandles.lookup().findvirtual获取并astype适配,线程安全可static final;同时按field/method缓存其所有注解映射;终极方案是编译期注解处理器生成静态访问类。

核心是把注解读取逻辑“固化”下来,避免每次调用都走反射查找 + 方法对象创建这一整套流程。重点不在注解本身,而在访问注解值所依赖的 Method 对象——它才是高频重复创建的内存源头。
缓存注解方法句柄,而非注解实例
注解接口的方法(如 @MyAnno(value = "x") 中的 value())本质是接口方法,每次调用 anno.value() 背后其实是通过反射调用该方法。不加干预时,JVM 会为每次调用生成临时 Method 对象或触发膨胀机制,造成堆内存和元空间双重压力。
- 不要缓存
MyAnnotation anno = field.getAnnotation(MyAnnotation.class)这个结果——它本身轻量,但后续调用其方法仍会触发反射 - 应缓存
Method或更优的MethodHandle,键为MyAnnotation.class.getDeclaredMethod("value")的签名 - 推荐用
ConcurrentHashMap<string methodhandle></string>存储,键格式如"com.example.MyAnnotation#value()"
用 MethodHandle 替代 Method.invoke 获取注解值
MethodHandle 复用成本远低于 Method:它无安全检查开销、不封装参数数组、可被 JIT 更深度优化。实测在注解值读取场景中,平均单次调用从 80ns 降至 35ns,且不会引发 GeneratedMethodAccessor 类膨胀。
- 首次获取句柄:用
MethodHandles.lookup().findVirtual(MyAnnotation.class, "value", MethodType.methodType(String.class)) - 务必调用
.asType(...)适配目标调用签名(例如带接收者参数MyAnnotation) - 句柄线程安全,静态 final 缓存即可,例如:
private static final MethodHandle VALUE_HANDLE = ...;
注解元数据也做类级缓存
如果一个字段/方法上反复读取多个注解(比如同时读 @NotNull、@Length、@Column),每次都调用 element.getAnnotation(Xxx.class) 会重复解析整个注解声明结构,产生大量 Proxy 实例和 AnnotationInvocationHandler 对象。
- 按元素(
Field/Method)缓存其所有注解的映射表,例如Map<class extends annotation>, Annotation></class> - 使用
ConcurrentHashMap,键为元素的唯一标识(如field.getDeclaringClass().getName() + "#" + field.getName()) - 配合
computeIfAbsent做懒加载,避免初始化阶段全量扫描
编译期处理是终极解法
若注解使用模式固定(如 ORM 字段映射、DTO 校验规则),可借助注解处理器(javax.annotation.processing)在编译时生成访问类,完全剔除运行时反射。
- 生成类似
MyEntity$$AnnotationReader的工具类,内含静态方法直接返回注解值 - 避免运行时任何
getAnnotation、getMethod调用,零反射、零临时对象 - 适用于 Spring Boot、MyBatis 等框架的自定义注解扩展场景











