注解信息存于class文件attributes区的RuntimeVisibleAnnotations(RUNTIME)或RuntimeInvisibleAnnotations(CLASS)表中;反射getAnnotation()读取的是类加载时从该表解析并缓存的AnnotationData,故必须用RetentionPolicy.RUNTIME且类已加载。

Java 注解的底层原理,常考且易错的核心结论,其实就集中在字节码层面的“存哪”和“怎么读”两个问题上。只要抓住这两个点,面试或实战中关于注解失效、反射拿不到、APT 不触发等高频问题,基本都能快速定位。
注解信息存在 class 文件的哪个位置?
它不存于类结构体(如 fields/methods)里,而是独立写在 class 文件的 attributes 区域中,具体是两个专用属性表:
-
RuntimeVisibleAnnotations:存放
@Retention(RetentionPolicy.RUNTIME)的注解; -
RuntimeInvisibleAnnotations:存放
@Retention(RetentionPolicy.CLASS)的注解。
这两个表在 javap -v 反编译时能直接看到,是判断注解是否“物理存在”于运行时的铁证。SOURCE 级注解连这两个表都不会进,编译完就彻底消失。
运行时反射 getAnnotation() 到底做了什么?
不是每次调用都去解析字节码,而是在类加载阶段,JVM 就已从 RuntimeVisibleAnnotations 表中把注解数据解析成内存对象,并缓存在 AnnotationData 结构里。后续反射调用只是返回这个预加载结果——所以:
立即学习“Java免费学习笔记(深入)”;
- 没有
@Retention(RUNTIME),类加载时就不会加载注解,反射永远为空; - 即使写了 RUNTIME,若类没被加载(比如动态代理未触发 loadClass),也读不到;
- 反射性能开销极小,本质是查缓存,不是实时解析。
为什么 @Target 在编译期就报错,而 @Retention 不会?
因为 @Target 是给 javac 编译器看的校验规则:它在 AST 解析阶段比对元素类型(如方法 vs 字段),不满足就直接中断编译;而 @Retention 是告诉 javac “把注解写到哪张属性表”,属于字节码生成策略,不参与语法/语义校验,所以不会导致编译失败,只影响后续能否读取。
自定义注解的属性值为什么只能是编译期常量?
因为所有注解数据在编译时就被固化进字节码的常量池(Constant Pool)中,属性值必须能表达为 CONSTANT_Utf8_info、CONSTANT_Class_info 或其他常量项。这也是为什么不能用 Integer、List、new Object() ——它们无法在编译期确定内存地址和结构,JVM 加载时没法安全还原。


















