跳转至

反射性能底层原理与 MethodHandle

一句话记忆口诀:反射慢在安全检查 + 无法 JIT 内联 + 参数装箱;MethodHandle 可被 JIT 内联性能接近直接调用;VarHandle 替代 Unsafe 做字段原子操作;动态代理慢在字节码生成,运行时调用走 InvocationHandler 反射链。


0. 反射是什么?反射主要针对什么内容?

0.1 反射的定义与核心价值

反射(Reflection) 是 Java 语言提供的一种运行时(Runtime) 动态获取和操作类信息的能力。它允许程序在运行时:

  • 获取类的完整结构信息(类名、父类、接口、注解等)
  • 动态创建对象实例(无需 new 关键字)
  • 访问和修改字段值(包括私有字段)
  • 调用方法(包括私有方法)
  • 操作数组和泛型
反射的核心价值:
编译期未知 → 运行期动态发现和操作
硬编码依赖 → 配置化、插件化架构
框架的基石:Spring IoC、MyBatis、Jackson 等

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&lt;?&gt;] --> 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 阶段。看似"越快越好",实际存在几点副作用:

  1. Metaspace 压力增大 ⚠️:每个被反射调用过的方法都会立刻产生一个 GeneratedMethodAccessorXXX 类。对那些"只反射几次就不再使用"的场景(如启动期的注解扫描、配置解析),这些类属于纯粹浪费,在 Metaspace 较小的生产环境下甚至可能触发 OutOfMemoryError: Metaspace
  2. 启动时间变长 ⚠️:字节码生成本身存在成本(拼装 class 文件 + defineClass + 类初始化)。对一次性反射调用,JNI 反而更快。
  3. 首次调用延迟升高:第一次调用必须承担生成类的代价,对延迟敏感的系统(如交易系统的首笔请求)会出现明显的延迟尖刺。
  4. 破坏 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 启动参数中添加:

--add-opens java.base/java.lang=ALL-UNNAMED
或在模块描述符 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?

Java 字段访问技术演进:
反射字段访问(Field.get/set) → Unsafe 原子操作 → VarHandle(Java 9+)
    ↓                    ↓                    ↓
通用但慢          危险但快          安全且快

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 的典型代表是:StampedLockConcurrentHashMap 的部分字段原子操作、以及 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(接口,可多实现)

查看生成的代理类字节码

// Java 8:通过系统属性保存代理类到磁盘
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

// Java 9+:
System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");

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 代理?

final 类:无法被继承 → 无法生成子类 → 代理失败
final 方法:无法被覆盖 → 子类无法拦截该方法 → 该方法的代理失效

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 的典型代表是 StampedLockConcurrentHashMap 的部分字段。

Q4:JDK 动态代理为什么只能代理接口?

JDK 动态代理通过 ProxyGenerator 在运行时生成代理类字节码,生成的代理类结构为 class $Proxy0 extends Proxy implements 目标接口。由于 Java 单继承限制,代理类已经继承了 Proxy 类,无法再继承目标实现类,因此只能通过实现接口来代理。CGLIB 则通过 ASM 生成目标类的子类来实现代理,不需要接口,但 final 类和 final 方法无法被代理(无法被继承/覆盖)。


一句话记忆口诀:反射慢在三点(无 JIT 内联、权限检查、参数装箱),setAccessible 解决权限检查,MethodHandle 解决内联问题,VarHandle 替代 Unsafe 做原子操作,JDK 代理继承 Proxy 所以只能代理接口,CGLIB 生成子类所以不能代理 final