反射性能底层原理与 MethodHandle¶
一句话记忆口诀:反射慢在安全检查 + 无法 JIT 内联 + 参数装箱;
MethodHandle可被 JIT 内联性能接近直接调用;VarHandle替代Unsafe做字段原子操作;动态代理慢在字节码生成,运行时调用走InvocationHandler反射链。
0. 反射是什么?反射主要针对什么内容?¶
0.1 反射的定义与核心价值¶
反射(Reflection) 是 Java 语言提供的一种运行时(Runtime) 动态获取和操作类信息的能力。它允许程序在运行时:
- 获取类的完整结构信息(类名、父类、接口、注解等)
- 动态创建对象实例(无需
new关键字) - 访问和修改字段值(包括私有字段)
- 调用方法(包括私有方法)
- 操作数组和泛型
0.2 反射主要针对的四大内容¶
① 类(Class)信息反射¶
// 获取类信息
Class<?> clazz = Class.forName("com.example.User");
String className = clazz.getName(); // 类名
Class<?> superClass = clazz.getSuperclass(); // 父类
Class<?>[] interfaces = clazz.getInterfaces(); // 实现的接口
int modifiers = clazz.getModifiers(); // 访问修饰符
Annotation[] annotations = clazz.getAnnotations(); // 注解
② 字段(Field)反射¶
// 访问字段
Field[] fields = clazz.getDeclaredFields(); // 所有字段(包括私有)
Field nameField = clazz.getDeclaredField("name"); // 特定字段
nameField.setAccessible(true); // 访问私有字段
Object value = nameField.get(user); // 读取字段值
nameField.set(user, "新值"); // 修改字段值
③ 方法(Method)反射¶
// 调用方法
Method[] methods = clazz.getDeclaredMethods(); // 所有方法
Method getName = clazz.getDeclaredMethod("getName"); // 无参方法
Method setName = clazz.getDeclaredMethod("setName", String.class); // 带参方法
setName.setAccessible(true); // 访问私有方法
Object result = getName.invoke(user); // 调用方法
setName.invoke(user, "新名字"); // 带参数调用
④ 构造器(Constructor)反射¶
// 创建实例
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
Constructor<?> defaultConstructor = clazz.getDeclaredConstructor(); // 无参构造
Constructor<?> paramConstructor = clazz.getDeclaredConstructor(String.class);
Object instance1 = defaultConstructor.newInstance(); // 无参构造实例
Object instance2 = paramConstructor.newInstance("初始值"); // 带参构造实例
0.3 反射在框架中的典型应用¶
| 框架 | 反射应用场景 | 针对内容 |
|---|---|---|
| Spring IoC | Bean 实例化、依赖注入 | 构造器、字段、方法 |
| MyBatis | ResultSet 到 POJO 映射 | 字段、setter 方法 |
| Jackson/Gson | JSON 序列化/反序列化 | 字段、getter/setter |
| JUnit | 测试方法发现和执行 | 方法、注解 |
| Hibernate | 实体类映射、懒加载 | 字段、方法 |
| Spring AOP | 切面代理生成 | 方法、注解 |
0.4 反射 API 核心类关系图¶
graph TB
A[Class<?>] --> B[Field]
A --> C[Method]
A --> D[Constructor]
A --> E[Annotation]
B --> B1[Field.get/set]
C --> C1[Method.invoke]
D --> D1[Constructor.newInstance]
F[Reflection] --> A
F --> G[Array 数组操作]
F --> H[Modifier 修饰符] 反射的性能代价
正是因为反射提供了如此强大的动态能力,它才需要付出性能代价。安全性检查、无法 JIT 内联、参数装箱是反射慢的三大核心原因,这也是本文后续章节重点分析的内容。
1. 引入:反射为什么慢?¶
反射是 Java 框架的基石——Spring IoC、MyBatis、Jackson 无不依赖反射。但反射调用比直接调用慢,这是面试高频问题,也是框架设计中必须权衡的核心问题。
工作中的典型场景¶
| 场景 | 反射的使用方式 |
|---|---|
| Spring IoC 容器 | 反射实例化 Bean、注入依赖 |
| Jackson/Gson 序列化 | 反射读写字段值 |
| MyBatis ResultMap | 反射调用 setter 填充结果集 |
| JUnit 测试框架 | 反射调用测试方法 |
| RPC 框架(Dubbo) | 反射调用服务实现方法 |
2. 反射性能开销的底层原因¶
2.1 原因一:JVM 无法对反射调用进行 JIT 内联¶
直接方法调用在 JIT 编译后会被内联(inline)——即把被调用方法的字节码直接嵌入调用处,消除方法调用开销。
// 直接调用 —— JIT 可内联
obj.hello()
↓ JIT 内联后
// "hello" 方法体直接展开在调用处,无跳转开销
// 反射调用 —— JIT 难以内联
method.invoke(obj, args)
↓ Java 17 及之前的实际执行链
Method.invoke()
→ DelegatingMethodAccessorImpl.invoke()
→ NativeMethodAccessorImpl.invoke() // 前 15 次:JNI 本地调用
→ (第 16 次起) GeneratedMethodAccessorXXX.invoke() // 字节码生成的访问器
📌 版本差异提醒:上图的“前 15 次 JNI + 第 16 次生成字节码”属于 JDK 17 及之前 的 Oracle/OpenJDK 实现。JDK 18 起 (JEP 416:以
MethodHandle重实现反射),Method.invoke已改为统一走MethodHandle连接器路径,不再存在 JNI 阶段。
反射调用经过多层委托,JIT 难以追踪真实调用目标,无法做内联优化,每次调用都有额外的方法分派开销。
膨胀阈值(inflation threshold)
| 方案 | 优点 | 缺点 |
|---|---|---|
| JNI 本地调用(前 15 次) | 无需生成类,启动快、内存占用低 | 每次有 JNI 边界开销,JIT 无法内联 |
| 生成字节码 Accessor(第 16 次起) | 第 16 次起自动生成专用的字节码访问器(GeneratedMethodAccessorXXX)纯 Java 调用,JIT 可内联,性能高 | 生成类有初始化开销 + 占用 Metaspace |
这种设计契合了 JVM 的一贯哲学:冷方法(调用少)使用 JNI 以节省内存;热方法(反复调用)升级为字节码 Accessor 以换取性能。
// NativeMethodAccessorImpl 内部(简化示意)
class NativeMethodAccessorImpl extends MethodAccessorImpl {
private int numInvocations = 0;
private static int inflationThreshold = 15; // 默认阈值
public Object invoke(Object obj, Object[] args) {
if (++numInvocations > inflationThreshold) {
// 超过阈值,生成字节码版本的 Accessor 并替换委托
MethodAccessorImpl acc = (MethodAccessorImpl)
new MethodAccessorGenerator().generateMethod(...);
parent.setDelegate(acc);
}
return invoke0(method, obj, args); // 前 15 次走 JNI
}
}
可通过 -Dsun.reflect.inflationThreshold=0 强制跳过 JNI 阶段。 -Dsun.reflect.inflationThreshold=0 意味着第一次反射调用就立即生成字节码 Accessor,完全跳过 JNI 阶段。看似"越快越好",实际存在几点副作用:
- Metaspace 压力增大 ⚠️:每个被反射调用过的方法都会立刻产生一个
GeneratedMethodAccessorXXX类。对那些"只反射几次就不再使用"的场景(如启动期的注解扫描、配置解析),这些类属于纯粹浪费,在 Metaspace 较小的生产环境下甚至可能触发OutOfMemoryError: Metaspace。 - 启动时间变长 ⚠️:字节码生成本身存在成本(拼装 class 文件 +
defineClass+ 类初始化)。对一次性反射调用,JNI 反而更快。 - 首次调用延迟升高:第一次调用必须承担生成类的代价,对延迟敏感的系统(如交易系统的首笔请求)会出现明显的延迟尖刺。
- 破坏 JVM 自适应优化:默认值 15 是 JVM 团队大量调优得到的经验值,强制设为 0 相当于否决了这一判断。
与其调整 inflationThreshold,不如从代码层面升级
// ① 缓存 Method 对象(最基本的优化)
private static final Method METHOD = MyClass.class.getDeclaredMethod("foo");
// ② MethodHandle(高频场景首选,见第 3 节)
private static final MethodHandle MH = MethodHandles.lookup()
.findVirtual(MyClass.class, "foo", MethodType.methodType(void.class));
// ③ LambdaMetafactory 生成函数式接口(性能最佳,invokedynamic 底层)
框架必须缓存 Method 对象
如果每次都通过 clazz.getDeclaredMethod(...) 获取新的 Method 实例,计数器永远从 0 开始,始终走 JNI 慢路径,无法升级到字节码 Accessor。这也是 Spring、Jackson、MyBatis 等框架必须缓存 Method 对象的核心原因之一。
2.2 原因二:每次调用都需要安全权限检查¶
// Method.invoke() 源码(简化)
public Object invoke(Object obj, Object... args) {
// ① 每次调用都检查访问权限
if (!override) {
// 检查调用方是否有权限访问该方法
// 涉及 Class 的 checkAccess,走 AccessController 安全栈遍历
checkAccess(Reflection.getCallerClass(), ...);
}
return methodAccessor.invoke(obj, args);
}
checkAccess 需要遍历调用栈来确定调用方的 Class,这是一个相对昂贵的操作。
setAccessible(true) 的作用:将 override 标志设为 true,跳过上述权限检查,是反射性能优化的第一步。
Method method = MyClass.class.getDeclaredMethod("privateMethod");
method.setAccessible(true); // 跳过权限检查,性能提升约 20%~50%
method.invoke(obj);
Java 9 模块系统的限制
Java 9 引入 JPMS(模块系统)后,setAccessible(true) 对跨模块的访问受到限制。若目标类在未开放的模块中,会抛出 InaccessibleObjectException。
解决方案:在 JVM 启动参数中添加:
或在模块描述符module-info.java 中声明 opens 指令。 2.3 原因三:参数装箱为 Object[] 导致额外堆分配¶
// 直接调用 —— 无装箱
obj.add(1, 2); // int 直接传递,栈上操作
// 反射调用 —— 必须装箱
method.invoke(obj, new Object[]{1, 2});
// ↑ 创建 Object 数组
// ↑ int 自动装箱为 Integer
// 每次调用都产生额外的堆对象,增加 GC 压力
2.4 JMH 基准测试数据¶
JMH 是 Java Microbenchmark Harness(Java 微基准测试套件),是 OpenJDK 官方团队(也就是开发 HotSpot JVM 的团队)出品的、专门用于在 JVM 上做微基准测试(microbenchmark)的工具框架。
Benchmark Mode Cnt Score Error Units
DirectCall.direct avgt 10 1.2 ± 0.1 ns/op
ReflectionBenchmark.withAccessible avgt 10 18.5 ± 0.5 ns/op
ReflectionBenchmark.noAccessible avgt 10 35.2 ± 1.2 ns/op
MethodHandleBenchmark.mh avgt 10 2.1 ± 0.1 ns/op
📌 声明:以上数据仅为示意性量级(来自社区综合基准的经验值),具体数字因 JVM 版本、硬件、方法签名而异,实际基准请自行跑 JMH 验证。
结论
- 直接调用:~1 ns(基准)
MethodHandle:~2 ns(接近直接调用,可被 JIT 内联)- 反射 +
setAccessible(true):~18 ns(约慢 15 倍) - 反射(无
setAccessible):~35 ns(约慢 30 倍)
3. 方法调用的性能优化:从反射到 MethodHandle(Java 7+)¶
3.1 技术演进:为什么需要 MethodHandle?¶
Java 动态方法调用技术演进:
反射(Java 1.1) → MethodHandle(Java 7) → LambdaMetafactory(Java 8)
↓ ↓ ↓
通用但慢 性能优化方案 最佳性能方案
MethodHandle 是 Java 7 引入的类型安全的方法引用,专门解决反射方法调用的性能问题。
3.2 MethodHandle vs 反射的核心差异¶
| 对比维度 | 反射(Method.invoke) | MethodHandle |
|---|---|---|
| 权限检查时机 | 每次调用都检查 | 获取时检查一次 |
| JIT 内联能力 | ❌ 不支持 | ✅ 支持 |
| 参数处理 | 装箱为 Object[] | invokeExact 无装箱 |
| 调用链 | 多层委托,JIT 难追踪 | 直接分派,JIT 可内联 |
| 适用场景 | 框架初始化、低频调用 | 高频调用路径 |
3.3 核心 API 与使用示例¶
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
public class MethodHandleDemo {
// ===== 1. 调用实例方法(findVirtual)=====
public static void virtualExample() throws Throwable {
// Lookup 是获取 MethodHandle 的工厂,权限检查在此处进行(仅一次)
MethodHandles.Lookup lookup = MethodHandles.lookup();
// findVirtual(类, 方法名, 方法类型)
// MethodType.methodType(返回类型, 参数类型...)
MethodHandle mh = lookup.findVirtual(
String.class,
"substring",
MethodType.methodType(String.class, int.class, int.class)
);
// invokeExact:严格类型匹配,性能最好
String result = (String) mh.invokeExact("Hello, World!", 0, 5);
System.out.println(result); // Hello
}
// ===== 2. 调用静态方法(findStatic)=====
public static void staticExample() throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle mh = lookup.findStatic(
Integer.class,
"parseInt",
MethodType.methodType(int.class, String.class)
);
int value = (int) mh.invokeExact("42");
System.out.println(value); // 42
}
// ===== 3. 访问私有方法(需要 privateLookupIn,Java 9+)=====
public static void privateExample() throws Throwable {
// Java 9+ 推荐方式,替代 setAccessible
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(
MyClass.class,
MethodHandles.lookup()
);
MethodHandle mh = lookup.findVirtual(
MyClass.class,
"privateMethod",
MethodType.methodType(void.class)
);
mh.invoke(new MyClass());
}
}
3.4 为什么 MethodHandle 可以被 JIT 内联?¶
反射调用链(JIT 难以追踪):
method.invoke(obj, args)
→ Method.invoke()
→ DelegatingMethodAccessorImpl
→ NativeMethodAccessorImpl / GeneratedAccessorXXX
→ 真实方法(JIT 看不到这里)
MethodHandle 调用链(JIT 可追踪):
mh.invokeExact(obj, args)
→ JVM 内部直接分派到目标方法
→ 真实方法(JIT 可内联展开)
MethodHandle 的调用在 JVM 规范层面有专门的字节码指令(invokedynamic)支持,JIT 编译器能识别并内联目标方法体。
invokedynamic 与 Lambda
Java 8 的 Lambda 表达式底层也是通过 invokedynamic + MethodHandle 实现的,这也是 Lambda 调用性能接近直接调用的原因。
4. 字段访问的性能优化:从反射到 VarHandle(Java 9+)¶
4.1 技术演进:为什么需要 VarHandle?¶
VarHandle 在反射文档中的定位:它是反射字段访问的性能优化方案,专门解决反射字段读写和原子操作的性能问题。
4.2 反射字段访问的性能问题¶
// ===== 反射字段访问(性能较差) =====
Field field = obj.getClass().getDeclaredField("count");
field.setAccessible(true);
int value = (int) field.get(obj); // 每次调用都有权限检查+类型转换
field.set(obj, value + 1); // 非原子操作
// ===== VarHandle 字段访问(性能优化) =====
VarHandle countHandle = MethodHandles.lookup()
.findVarHandle(obj.getClass(), "count", int.class);
int value = (int) countHandle.get(obj); // 性能接近直接访问
countHandle.getAndAdd(obj, 1); // 原子操作,无锁
4.3 VarHandle 的核心价值:提供 Unsafe 的官方替代¶
在 Java 9 之前,JDK 内部大量使用 sun.misc.Unsafe 进行字段的原子操作(CAS)。sun.misc.Unsafe 是非公开 API,存在安全风险,且在 Java 9 模块系统下对用户代码逐步关闭。Java 9 引入 VarHandle 作为官方等价替代。
⚠️ 一个容易被传错的细节:并非所有
Atomic*类都在 JDK 9+ 迁移到了VarHandle。截至 JDK 21,java.util.concurrent.atomic.AtomicInteger/AtomicLong/AtomicReference的 OpenJDK 源码仍然使用jdk.internal.misc.Unsafe(注意不是sun.misc.Unsafe,而是 JDK 内部特权版本),没有改用VarHandle。真正完全使用VarHandle的典型代表是:StampedLock、ConcurrentHashMap的部分字段原子操作、以及 JDK 9+ 新增/重写的VarHandle.VarForm相关类。
下面用一个 自定义的 MyAtomicInteger 对比两种写法(而不直接以 JDK 自带类命名,避免误导):
// ===== JDK 8 户多见的写法:基于 Unsafe(仅可在 JDK 内部或通过反射获取) =====
public class MyAtomicInteger {
private static final Unsafe unsafe = Unsafe.getUnsafe(); // 需 Bootstrap ClassLoader
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset(
MyAtomicInteger.class.getDeclaredField("value")
);
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
}
// ===== JDK 9+ 推荐写法:基于 VarHandle(用户代码的官方途径) =====
public class MyAtomicInteger {
private static final VarHandle VALUE;
static {
try {
VALUE = MethodHandles.lookup()
.findVarHandle(MyAtomicInteger.class, "value", int.class);
} catch (ReflectiveOperationException e) { throw new Error(e); }
}
private volatile int value;
public final boolean compareAndSet(int expectedValue, int newValue) {
return VALUE.compareAndSet(this, expectedValue, newValue);
}
}
4.4 VarHandle 核心操作¶
import java.lang.invoke.VarHandle;
import java.lang.invoke.MethodHandles;
public class VarHandleDemo {
private int count = 0;
private static final VarHandle COUNT;
static {
try {
COUNT = MethodHandles.lookup()
.findVarHandle(VarHandleDemo.class, "count", int.class);
} catch (ReflectiveOperationException e) {
throw new Error(e);
}
}
public void demo() {
VarHandleDemo obj = new VarHandleDemo();
// 1. 普通读写(替代 Field.get/set)
COUNT.set(obj, 10);
int val = (int) COUNT.get(obj);
// 2. volatile 语义读写(内存屏障)
COUNT.setVolatile(obj, 20);
int vVal = (int) COUNT.getVolatile(obj);
// 3. CAS 操作(原子比较并交换)
boolean success = COUNT.compareAndSet(obj, 20, 30);
// 4. 原子加法(getAndAdd)
int old = (int) COUNT.getAndAdd(obj, 5);
}
}
VarHandle vs Unsafe
| 对比维度 | Unsafe | VarHandle |
|---|---|---|
| API 可见性 | 非公开(sun.misc) | 公开标准 API |
| 类型安全 | 否(操作内存偏移量) | 是(类型检查) |
| 模块系统兼容 | Java 9+ 受限 | 完全兼容 |
| 性能 | 极高 | 接近 Unsafe |
| 推荐使用 | 不推荐(框架内部) | 推荐 |
5. 动态代理字节码生成原理(补充)¶
本节作为补充内容,聚焦于字节码生成原理。代理模式的使用方式详见 @dp-代理模式。
5.1 JDK 动态代理:ProxyGenerator 生成字节码¶
Proxy.newProxyInstance(loader, interfaces, handler) 执行流程:
1. 检查缓存(WeakCache)
└─ 已生成过该接口组合的代理类?直接返回
2. ProxyGenerator.generateProxyClass()
└─ 在内存中生成字节码(.class 文件格式)
└─ 生成的代理类结构:
┌─────────────────────────────────────────┐
│ public final class $Proxy0 │
│ extends Proxy │ ← 已继承 Proxy
│ implements OrderService { │ ← 实现目标接口
│ │
│ // 每个接口方法都生成对应实现 │
│ public void createOrder(Order o) { │
│ h.invoke(this, m1, new Object[]{o}); │
│ } │
│ } │
└─────────────────────────────────────────┘
3. ClassLoader.defineClass() 将字节码加载到 JVM
4. 返回代理类实例
为什么只能代理接口?
Java 单继承限制:
$Proxy0 extends Proxy → 已占用唯一的父类位置
无法再 extends OrderServiceImpl
只能 implements OrderService(接口,可多实现)
查看生成的代理类字节码
5.2 CGLIB:ASM 生成目标类的子类¶
Enhancer.create() 执行流程:
1. ASM 字节码框架读取目标类(OrderServiceImpl)的字节码
2. 生成子类字节码:
┌─────────────────────────────────────────────────┐
│ public class OrderServiceImpl$$EnhancerByCGLIB │
│ extends OrderServiceImpl { │ ← 继承目标类
│ │
│ // 覆盖所有非 final 方法 │
│ @Override │
│ public void createOrder(Order o) { │
│ MethodInterceptor interceptor = ...; │
│ interceptor.intercept(this, method, │
│ new Object[]{o}, methodProxy); │
│ } │
│ } │
└─────────────────────────────────────────────────┘
3. 通过 ClassLoader 加载生成的子类
4. 返回子类实例(可直接赋值给 OrderServiceImpl 变量)
为什么 final 类/方法无法被 CGLIB 代理?
CGLIB 的 FastClass 机制
CGLIB 调用父类方法时使用 MethodProxy.invokeSuper(),底层通过 FastClass 机制(为每个方法生成索引,直接通过索引调用,避免反射)实现,性能优于 JDK 动态代理的 method.invoke()。
5.3 两种动态代理性能对比¶
代理方式性能对比(方法调用阶段):
直接调用 ████ 1x
CGLIB(FastClass) ████ ~1.2x
JDK 动态代理(JDK 8+ 优化) ████ ~1.5x
JDK 动态代理(早期版本) ████████ ~3x
注:生成代理类的初始化开销 CGLIB > JDK 动态代理
方法调用的运行时开销 CGLIB ≈ JDK 动态代理(JDK 8+ 后差距缩小)
6. 关键对比总结¶
技术演进图谱¶
Java 动态访问技术演进:
方法调用路径:
反射(Method.invoke) → MethodHandle → LambdaMetafactory
↓ ↓ ↓
通用但慢 性能优化方案 最佳性能方案
字段访问路径:
反射(Field.get/set) → Unsafe → VarHandle
↓ ↓ ↓
通用但慢 危险但快 安全且快
选型建议¶
flowchart TD
A[需要动态访问类成员] --> B{访问类型?}
B -->|方法调用| C{调用频率?}
B -->|字段访问| D{操作类型?}
C -->|低频/框架初始化| E[反射 + setAccessible]
C -->|高频/性能敏感| F{Java版本?}
F -->|Java 7+| G[MethodHandle]
F -->|Java 8+| H[LambdaMetafactory]
D -->|普通读写| I[反射Field.get/set]
D -->|原子操作| J{Java版本?}
J -->|Java 8-| K[Unsafe(不推荐)]
J -->|Java 9+| L[VarHandle(推荐)] 7. 总结:面试标准化表达¶
高频问题¶
Q1:反射为什么比直接调用慢?如何优化?
反射慢有三个底层原因:① JVM 无法对反射调用进行 JIT 内联优化,因为调用链经过多层委托,JIT 无法追踪真实目标;② 每次调用都需要进行安全权限检查(
checkAccess),涉及调用栈遍历;③ 参数需要装箱为Object[],产生额外堆分配和 GC 压力。优化方式:① 调用setAccessible(true)跳过权限检查;② 缓存Method对象避免重复查找;③ 高频场景改用MethodHandle(可被 JIT 内联)。
Q2:MethodHandle 和反射有什么区别?
MethodHandle是 Java 7 引入的类型安全方法引用,与反射的核心区别在于:①MethodHandle的权限检查在获取时只做一次,调用时无额外检查;②MethodHandle调用可被 JIT 编译器内联,性能接近直接调用;③invokeExact无需参数装箱,类型严格匹配。反射更适合框架初始化等低频场景,MethodHandle适合高频调用路径(Lambda 底层就是用invokedynamic+MethodHandle实现的)。
Q3:VarHandle 是什么?为什么要替代 Unsafe?
VarHandle(Java 9+)是对变量(字段、数组元素)进行原子操作的标准 API,用于提供sun.misc.Unsafe的官方替代。sun.misc.Unsafe是非公开 API,通过内存偏移量直接操作内存,绕过了 Java 类型系统,存在安全风险,且在 Java 9 模块系统下对用户代码逐步关闭。VarHandle提供了类型安全的 CAS、volatile 读写、原子加法等操作。需注意:截至 JDK 21,java.util.concurrent.atomic.AtomicInteger/AtomicLong等经典原子类并未迁移到VarHandle,而是改用 JDK 内部的jdk.internal.misc.Unsafe;完全使用VarHandle的典型代表是StampedLock与ConcurrentHashMap的部分字段。
Q4:JDK 动态代理为什么只能代理接口?
JDK 动态代理通过
ProxyGenerator在运行时生成代理类字节码,生成的代理类结构为class $Proxy0 extends Proxy implements 目标接口。由于 Java 单继承限制,代理类已经继承了Proxy类,无法再继承目标实现类,因此只能通过实现接口来代理。CGLIB 则通过 ASM 生成目标类的子类来实现代理,不需要接口,但final类和final方法无法被代理(无法被继承/覆盖)。
一句话记忆口诀:反射慢在三点(无 JIT 内联、权限检查、参数装箱),
setAccessible解决权限检查,MethodHandle解决内联问题,VarHandle替代Unsafe做原子操作,JDK 代理继承Proxy所以只能代理接口,CGLIB 生成子类所以不能代理final。