编译时注解处理器性能远超反射解析,差距达10倍以上:前者在编译期生成标准Java代码,无运行时开销,支持JVM内联优化;后者每次调用需动态查找、安全检查、参数包装及JNI跳转,冷启动延迟高、波动大且混淆敏感。

编译时注解处理器几乎不产生运行时开销,而反射解析在每次调用时都需动态查找、安全检查、参数包装与JVM额外跳转,性能差距可达10倍以上。
编译时注解处理器:代码生成即完成
注解处理器(如MapStruct、Lombok、ButterKnife的APT)在javac编译阶段扫描源码,识别@Retention(RetentionPolicy.SOURCE)或@Retention(RetentionPolicy.CLASS)注解,直接生成标准Java类文件(如UserMapperImpl.java)。这些类参与正常编译,最终字节码中全是普通方法调用,无任何元数据解析逻辑。
- 生成的代码可被JVM完全内联、优化,和手写代码一致
- 启动时间零影响——无需类扫描、无缓存预热
- 内存占用极低——不加载额外元数据,不维护反射缓存表
- 失败提前暴露——类型错误、空指针风险在编译期报错,而非运行时报NPE或NoSuchMethodException
运行时反射解析:每次调用都是“软中断”
以Class.getAnnotation()或Method.invoke()为代表的反射操作,必须在JVM运行时动态完成整套流程:定位类定义 → 解析字节码结构 → 检查访问权限 → 拆箱/装箱参数 → 触发JNI边界 → 执行目标方法。这个过程无法被JIT充分优化。
Miller (mlr) 是一个命令行工具,用于查询、整形和重新格式化名称索引数据,如 CSV、TSV、JSON 和 JSON Lines。它将 awk、sed、cut、join 和 sort 的功能整合到一个专为结构化数据处理而构建的单一工具中。
- 冷启动延迟明显:首次调用可能耗时数百纳秒,远超直接调用的几纳秒
- 缓存虽有但受限:即使框架内部缓存了Method对象,仍需同步控制、GC压力、失效策略
- 安全机制拖累:每次
setAccessible(true)或invoke都触发SecurityManager检查(即使未启用) - 混淆敏感:类名/方法名被ProGuard重命名后,反射路径立即失效,而编译期生成代码天然适配新名字
关键差异由RetentionPolicy决定
注解能否用于编译期处理,取决于它的保留策略:
-
SOURCE:仅存在源码中,适合APT生成绑定、校验等代码(如
@BindView) - CLASS:保留在.class文件,但不加载进JVM,可用于字节码增强(如某些AOP工具)
- RUNTIME:唯一支持反射读取的策略,但也是性能瓶颈源头——若本可用SOURCE+APT实现,却选RUNTIME,就人为引入运行时开销
实测场景对比(百万次映射)
以对象转换为例:
- MapStruct(编译时):平均单次耗时 ≈ 8–12 ns
- ModelMapper默认模式(反射):平均单次耗时 ≈ 120–350 ns(冷启动峰值超1μs)
- 手动反射+缓存优化后:仍稳定在80–150 ns,且依赖开发者正确管理缓存生命周期
差距不仅在于绝对数值,更在于稳定性——反射耗时波动大,受JVM状态、类加载顺序、GC时机显著影响;而编译期代码执行路径固定、可预测。


















