MethodHandle与VarHandle
# 27.MethodHandle与VarHandle
# 目录介绍
- 1. 案例引入
- 2. java.lang.invoke 全景
- 3. MethodHandle 句柄抽象
- 4. invokedynamic 与 LambdaMetafactory
- 5. VarHandle 内存语义革命
- 6. 性能实证
- 7. 实战案例
- 8. 使用边界与陷阱
- 9. 综合案例串讲
# 1. 案例引入
# 1.1 反射热路径的性能毒瘤
某 RPC 框架通过反射做参数填充,QPS 压测时火焰图惊现 Method.invoke 占据 35% CPU:
public class FastInvoker {
// 启动时缓存 Method 对象
private final Method method;
public FastInvoker(Class<?> clazz, String name) throws Exception {
this.method = clazz.getMethod(name, String.class);
this.method.setAccessible(true); // ★ 关闭访问检查
}
public Object invoke(Object target, String arg) throws Exception {
// ★ 热路径:每秒 100w 次调用
return method.invoke(target, arg);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
火焰图证据(10 万次调用,99 分位耗时):
直接调用 : 8 ns/op ( 1×, baseline)
缓存的反射 : 165 ns/op (20× 慢)
未缓存反射 : 1240 ns/op (155× 慢)
2
3
疑惑:
- Method 对象都已经缓存了,为什么还是这么慢?
- 反射调用时 JVM 内部到底做了什么?
- 有没有"反射级灵活 + 直接调用级性能"的中间方案?
# 1.2 Unsafe 退场的恐慌
某高性能并发库依赖 sun.misc.Unsafe 做 CAS 字段更新:
public class LongAdderCell {
private static final Unsafe UNSAFE;
private static final long VALUE_OFFSET;
static {
try {
// ★ 反射拿 Unsafe(JDK 17+ 强烈警告,JDK 24 可能彻底移除)
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
UNSAFE = (Unsafe) f.get(null);
VALUE_OFFSET = UNSAFE.objectFieldOffset(LongAdderCell.class.getDeclaredField("value"));
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
private volatile long value;
public boolean cas(long expect, long update) {
return UNSAFE.compareAndSwapLong(this, VALUE_OFFSET, expect, update);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
JDK 17 启动告警:
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by ... to field jdk.internal.misc.Unsafe.theUnsafe
WARNING: Please consider reporting this to the maintainers of ...
WARNING: All illegal reflective access operations will be denied in a future release
2
3
4
疑惑:
- Unsafe 真的会被移除吗?JDK 团队推荐用什么替代?
- "替代品"的内存语义和 Unsafe 等价吗?
- volatile 写、CAS、内存屏障,都怎么落到新 API 上?
# 1.3 我们要回答什么
第 31 篇是卷四第 2 篇,紧承 07 篇的反射机制(卷四第 1 篇),把"反射的现代继任者"完整讲透:
java.lang.invoke 双子星:
MethodHandle → 反射调用的现代替代(方法/构造器层面)
↓
与 invokedynamic / LambdaMetafactory / SwitchBootstraps 同源
VarHandle → Unsafe 的官方继任者(字段/数组层面)
↓
提供精细的内存语义控制(plain/opaque/acquire-release/volatile)
共同特征:JIT 友好 + 类型严格 + 性能接近直接调用
2
3
4
5
6
7
8
9
10
11
带着这个目标回答 7 个核心问题:
追问 ①:MethodHandle 比反射快多少?快在哪? → 第3.3、§6.1、§6.4
追问 ②:MethodType 是什么?为什么 invokeExact 这么严格? → 第3.1、§3.3
追问 ③:MethodHandle 与 invokedynamic 是什么关系? → 第4章
追问 ④:VarHandle 凭什么替代 Unsafe? → 第5章
追问 ⑤:四级内存语义到底有什么区别? → 第5.2
追问 ⑥:JMH 实测三种调用方式的真实差距? → 第6章
追问 ⑦:动态字段读取器/SwitchBootstraps/无锁队列怎么写? → 第7章
2
3
4
5
6
7
本篇路线:
java.lang.invoke 全景 (第2章) ─── 总览
↓
MethodHandle 句柄抽象 (第3章) ←—— 方法层
↓
invokedynamic / LambdaMetafactory (第4章) ←—— 与语言特性的桥
↓
VarHandle 内存语义革命 (第5章) ←—— 字段层
↓
性能实证 (第6章) ←—— JMH 数据
↓
实战案例 (第7章) ←—— 落地
↓
使用边界与陷阱 (第8章)
↓
综合案例串讲 (第9章)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 2. java.lang.invoke 全景
# 2.1 包结构与七大主角
java.lang.invoke 包诞生于 JDK 7(JSR 292),是为支持动态语言(JRuby/Groovy/Scala)而引入的"运行时句柄"基础设施:
java.lang.invoke 包结构:
MethodHandle 方法句柄(核心)
MethodHandles 工具集合(静态方法工厂)
MethodHandles.Lookup 查找器("权限令牌")
MethodType 方法类型签名(不可变)
CallSite 调用点(invokedynamic 的链接对象)
├─ ConstantCallSite 常量调用点(链接后不可改)
├─ MutableCallSite 可变调用点(target 可改)
└─ VolatileCallSite volatile 调用点(target 改变立即可见)
VarHandle 变量句柄(JDK 9 新增,替代 Unsafe)
LambdaMetafactory Lambda 引导工厂
StringConcatFactory 字符串拼接引导工厂(JDK 9)
SwitchBootstraps switch 模式引导工厂(JDK 21)
2
3
4
5
6
7
8
9
10
11
12
13
14
# 2.2 反射的进化时间线
JDK 1.1 (1997) java.lang.reflect 传统反射
JDK 7 (2011) MethodHandle / invokedynamic JSR 292 「Da Vinci Machine」
JDK 8 (2014) LambdaMetafactory Lambda 落地
JDK 9 (2017) VarHandle / StringConcatFactory Unsafe 替代 + 拼接优化
JDK 11 (2018) Nest-based Access 嵌套类访问优化
JDK 14 (2020) ConstantBootstraps 常量动态生成
JDK 21 (2023) SwitchBootstraps switch 模式底层 ★
2
3
4
5
6
7
# 2.3 与 invokedynamic 的同源关系
invokedynamic(简称 indy)是 JDK 7 引入的第 5 种方法调用字节码指令——前 4 种是 invokestatic / invokespecial / invokevirtual / invokeinterface(详见 13 篇)。
indy 指令的核心思想:
第一次执行:
调用 BootstrapMethod(引导方法),返回一个 CallSite
CallSite 内含 MethodHandle(真正执行的目标)
把 CallSite 与 indy 调用点绑定
后续执行:
直接通过 CallSite.target(MethodHandle)调用
绕过 BootstrapMethod,性能与直接调用接近
→ MethodHandle 不是孤立 API,它是 indy 的"调用目标"
→ Lambda、Switch 模式、字符串拼接,全部基于这个机制
2
3
4
5
6
7
8
9
10
11
12
13
# 3. MethodHandle 句柄抽象
# 3.1 MethodType 类型签名
MethodType 描述方法的"形状"——返回值类型 + 所有参数类型:
// String foo(int, double) → MethodType.methodType(String.class, int.class, double.class)
MethodType mt = MethodType.methodType(String.class, int.class, double.class);
System.out.println(mt); // (int,double)String
// 无参方法 void bar()
MethodType mt2 = MethodType.methodType(void.class); // ()void
// 数组参数 byte[] baz(byte[])
MethodType mt3 = MethodType.methodType(byte[].class, byte[].class);
2
3
4
5
6
7
8
9
特点:
- 不可变(immutable):所有变换返回新实例
- 驻留(interned):相同签名的 MethodType 是同一对象(
==比较成立) - JVM 内部表示:底层与
Class[]平行,常量池可直接引用
# 3.2 Lookup 五大解析器
MethodHandles.Lookup 是"权限令牌"——它持有调用方的访问权限,决定能查找哪些方法:
// 在调用方所在的类中调用,获取该类视角的 Lookup
MethodHandles.Lookup lookup = MethodHandles.lookup();
2
五大解析方法:
| 方法 | 等价于 | 用途 |
|---|---|---|
findStatic(class, name, type) | invokestatic | 静态方法 |
findVirtual(class, name, type) | invokevirtual | 实例方法(虚分派) |
findSpecial(class, name, type, caller) | invokespecial | super 调用 / private |
findConstructor(class, type) | invokespecial <init> | 构造器 |
findGetter / findSetter | getfield/putfield | 字段访问 |
示例:
public class Demo {
public static void main(String[] args) throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.lookup();
// ① findStatic:String.valueOf(int)
MethodType mt1 = MethodType.methodType(String.class, int.class);
MethodHandle mh1 = lookup.findStatic(String.class, "valueOf", mt1);
String s1 = (String) mh1.invoke(42); // "42"
// ② findVirtual:String.length()
MethodType mt2 = MethodType.methodType(int.class);
MethodHandle mh2 = lookup.findVirtual(String.class, "length", mt2);
int len = (int) mh2.invoke("hello"); // 5
// ③ findConstructor:new ArrayList(int)
MethodType mt3 = MethodType.methodType(void.class, int.class);
MethodHandle mh3 = lookup.findConstructor(ArrayList.class, mt3);
ArrayList<?> list = (ArrayList<?>) mh3.invoke(16);
// ④ findGetter:访问字段
MethodHandle mh4 = lookup.findGetter(Demo.class, "name", String.class);
}
private String name;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Lookup 的访问权限:
lookup() 在哪个类调用,就拥有哪个类的访问权限
→ 能访问该类的 private 成员(无需 setAccessible)
privateLookupIn(Target.class, lookup) → 借用 Target 的权限(需 Target 主动配合)
publicLookup() → 只能访问 public 成员
2
3
4
5
# 3.3 invoke vs invokeExact
这是 MethodHandle 最关键、也最容易踩坑的设计:
MethodHandle mh = lookup.findStatic(Math.class, "max",
MethodType.methodType(int.class, int.class, int.class));
// 方式 1:invokeExact —— 类型必须 100% 严格匹配
int r1 = (int) mh.invokeExact(3, 5); // ✅ OK
int r2 = (int) mh.invokeExact(3L, 5L); // ❌ WrongMethodTypeException
Integer r3 = (Integer) mh.invokeExact(3, 5); // ❌ 返回类型不严格匹配
// 方式 2:invoke —— 允许自动类型转换(asType)
int r4 = (int) mh.invoke(3L, 5L); // ✅ OK,自动 long → int 截断
Integer r5 = (Integer) mh.invoke(3, 5); // ✅ OK,自动 int → Integer 装箱
2
3
4
5
6
7
8
9
10
11
关键差异:
| 维度 | invokeExact | invoke |
|---|---|---|
| 类型检查 | 编译期 + 运行期严格匹配 | 运行期允许自动转换 |
| 性能 | 接近直接调用 | 多一次 asType 转换 |
| 错误类型 | WrongMethodTypeException | 自动尝试转换 |
为什么严格?因为 invokeExact 在字节码层面是多态签名(polymorphic signature)——签名由调用点决定,编译器要把 mh.invokeExact(3, 5) 编译成"返回 int、参数 (int,int)"的 indy 调用,类型不匹配则无法生成正确的字节码。
铁律:性能敏感路径必须用 invokeExact。
# 3.4 句柄变换 asType/bindTo/insertArguments
MethodHandle 是可组合的一等公民——能像函数一样变换、绑定、组合:
MethodHandle add = lookup.findStatic(Math.class, "addExact",
MethodType.methodType(int.class, int.class, int.class));
// ① asType:类型适配
MethodHandle longAdd = add.asType(MethodType.methodType(long.class, long.class, long.class));
long r1 = (long) longAdd.invokeExact(3L, 5L); // 8
// ② bindTo:绑定第一个参数(柯里化)
MethodHandle add5 = add.bindTo(5); // 注意:bindTo 仅适用于实例方法的 receiver
// ★ 静态方法用 insertArguments 更合适
// ③ insertArguments:插入参数
MethodHandle addTo10 = MethodHandles.insertArguments(add, 0, 10); // 把第 0 位固定为 10
int r2 = (int) addTo10.invokeExact(5); // 15
// ④ filterArguments:参数预处理
MethodHandle parseInt = lookup.findStatic(Integer.class, "parseInt",
MethodType.methodType(int.class, String.class));
MethodHandle addStrings = MethodHandles.filterArguments(add, 0, parseInt, parseInt);
int r3 = (int) addStrings.invokeExact("3", "5"); // 8
// ⑤ guardWithTest:条件分支
MethodHandle guarded = MethodHandles.guardWithTest(test, target, fallback);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
这套组合 API 让 MethodHandle 成为 JVM 上的"函数式编程基础设施"——比 Java 8 的 Function 更底层、更高效。
# 4. invokedynamic 与 LambdaMetafactory
# 4.1 CallSite 三种形态
CallSite 是 indy 调用点的"槽位"——内含一个 MethodHandle target:
public abstract class CallSite {
MethodHandle target;
public abstract MethodHandle dynamicInvoker();
public abstract void setTarget(MethodHandle newTarget); // 子类决定是否允许
}
2
3
4
5
三种实现:
| 子类 | 特征 | 用途 |
|---|---|---|
| ConstantCallSite | target 一旦设置就不可改 | Lambda(绑定后稳定) |
| MutableCallSite | target 可改,但更新非线程立即可见 | JRuby 等动态语言的 inline cache |
| VolatileCallSite | target 改变立即跨线程可见 | 罕见,用于热替换场景 |
# 4.2 引导方法 BootstrapMethod
每个 indy 指令都关联一个 BootstrapMethod(引导方法)——首次执行时调用,返回 CallSite:
// BootstrapMethod 签名约定(必须遵守)
public static CallSite bootstrap(
MethodHandles.Lookup lookup, // 调用方的 Lookup
String name, // 调用点名称
MethodType type, // 调用点签名
Object... args // 静态参数(来自常量池)
) { ... }
2
3
4
5
6
7
字节码层证据(用 javap -v 反编译 Lambda):
BootstrapMethods:
0: #N invokestatic java/lang/invoke/LambdaMetafactory.metafactory:
(Ljava/lang/invoke/MethodHandles$Lookup;
Ljava/lang/String;
Ljava/lang/invoke/MethodType;
Ljava/lang/invoke/MethodType;
Ljava/lang/invoke/MethodHandle;
Ljava/lang/invoke/MethodType;)
Ljava/lang/invoke/CallSite;
Method arguments:
#M ()V // 函数式接口签名
#X invokestatic Demo.lambda$0:()V // 真正执行的目标
#M ()V // 强制类型签名
2
3
4
5
6
7
8
9
10
11
12
13
# 4.3 LambdaMetafactory 落地链路
把 27 篇的 Lambda 与本篇的 MethodHandle 完整串起来:
flowchart TB
A["源码<br/>Runnable r = () -> System.out.println(hello)"] --> B["javac 编译"]
B --> C["字节码:indy 调用 LambdaMetafactory.metafactory<br/>+ Lambda 体编译为 lambda$0 私有方法"]
C --> D["首次执行:indy 触发 BootstrapMethod"]
D --> E["LambdaMetafactory.metafactory 返回 ConstantCallSite"]
E --> F["CallSite.target 是一个 MethodHandle<br/>该 MethodHandle 创建 Runnable 实例"]
F --> G["后续执行:直接 CallSite.dynamicInvoker.invoke<br/>性能接近直接 new 内部类"]
2
3
4
5
6
7
关键洞察:Lambda 不是创建 1 个对象——它创建的是"动态生成的 Runnable 子类的单例",LambdaMetafactory 通过 ASM 在运行时生成这个子类。详见 27 篇。
# 4.4 SwitchBootstraps 串讲
回扣 30 篇结尾的钩子——switch 模式底层:
String describe(Object obj) {
return switch (obj) {
case Integer i -> "int: " + i;
case String s -> "string: " + s;
case null -> "null!";
default -> "other";
};
}
2
3
4
5
6
7
8
字节码(简化):
aload_1
invokedynamic #N // ★ 引导方法 SwitchBootstraps.typeSwitch
// 静态参数:[Integer.class, String.class]
// 返回值:匹配的 case 索引(0 / 1 / -1 表 default)
tableswitch
0: int_branch
1: string_branch
-1: default_branch
2
3
4
5
6
7
8
SwitchBootstraps.typeSwitch 实现(位于 java.lang.runtime):
public static CallSite typeSwitch(
MethodHandles.Lookup lookup,
String invocationName,
MethodType invocationType,
Object... labels
) {
// labels 数组:[Integer.class, String.class]
// 返回 CallSite,target 是一个查表 MethodHandle
return new ConstantCallSite(...);
}
2
3
4
5
6
7
8
9
10
至此,从语言层 switch → 字节码 indy → 运行时 SwitchBootstraps → MethodHandle 链路 → 实际类型检查的完整链条全部打通。
# 5. VarHandle 内存语义革命
# 5.1 为什么 Unsafe 必须退场
sun.misc.Unsafe 长期是高性能并发库的"基础设施"——AtomicInteger、ConcurrentHashMap、LongAdder、Disruptor 等无一例外依赖它。但 Unsafe 有 4 大原罪:
原罪 ①:包名 sun.* —— 不是公开 API,理论上 JVM 实现自由变更
原罪 ②:内存安全 —— 直接操作内存地址,越界即 JVM crash
原罪 ③:模块化破坏者 —— 绕过 JPMS 所有限制
原罪 ④:性能不一定最优 —— JIT 对 Unsafe 的优化次于 intrinsic
2
3
4
JDK 9 引入 VarHandle 作为官方继任者,提供与 Unsafe 等价的能力 + 更精细的内存语义控制 + JIT 友好。
# 5.2 四级访问模式
VarHandle 把内存访问按"语义强度"分为 4 级——这是 JDK 9 借鉴 Java Memory Model 与 C++11 atomic 的成果:
访问模式(由弱到强):
① Plain —— 普通读写,无任何同步保证(等价普通字段访问)
② Opaque —— 保证不被 JIT 重排到无关代码外,但不跨线程可见
③ Acquire/Release —— 单向屏障(acquire 防后续重排到前/release 防前面重排到后)
④ Volatile —— 全屏障(等价 volatile 读写)
2
3
4
5
6
API 全景:
public class Counter {
private static final VarHandle VALUE;
static {
try {
VALUE = MethodHandles.lookup()
.findVarHandle(Counter.class, "value", long.class);
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
private long value;
public void demo() {
// ① Plain
long v1 = (long) VALUE.get(this);
VALUE.set(this, 1L);
// ② Opaque
long v2 = (long) VALUE.getOpaque(this);
VALUE.setOpaque(this, 2L);
// ③ Acquire/Release
long v3 = (long) VALUE.getAcquire(this);
VALUE.setRelease(this, 3L);
// ④ Volatile
long v4 = (long) VALUE.getVolatile(this);
VALUE.setVolatile(this, 4L);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
何时选哪个:
| 场景 | 推荐 | 理由 |
|---|---|---|
| 普通字段 | Plain | 性能最高 |
| flag 标志位(弱可见) | Opaque | 保证读到最终值,不一定立即可见 |
| Disruptor 序号发布 | Release/Acquire | 单向屏障,性能优于 volatile |
| 共享状态(强可见) | Volatile | 等价 volatile 字段 |
# 5.3 CAS 与 weakCompareAndSet
VarHandle 提供完整的 CAS 家族:
public class AtomicCounter {
private static final VarHandle VALUE;
static {
try {
VALUE = MethodHandles.lookup()
.findVarHandle(AtomicCounter.class, "value", long.class);
} catch (Exception e) { throw new ExceptionInInitializerError(e); }
}
private volatile long value;
// ① compareAndSet:强 CAS,全屏障
public boolean cas(long expected, long update) {
return VALUE.compareAndSet(this, expected, update);
}
// ② weakCompareAndSet:可能虚假失败,但性能更高(在循环中很合适)
public long incrementWeak() {
long current;
do {
current = (long) VALUE.getVolatile(this);
} while (!VALUE.weakCompareAndSet(this, current, current + 1));
return current + 1;
}
// ③ compareAndExchange:返回当前值,失败时不重试
public long casOrCurrent(long expected, long update) {
return (long) VALUE.compareAndExchange(this, expected, update);
}
// ④ getAndAdd:原子加并返回旧值
public long incrementAndGet() {
return (long) VALUE.getAndAdd(this, 1L) + 1;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 5.4 内存屏障 fence API
VarHandle 还提供独立的内存屏障 API(在 MethodHandles 中):
// loadLoad + loadStore:防止后续读重排到前面
VarHandle.acquireFence();
// storeStore + loadStore:防止前面写重排到后面
VarHandle.releaseFence();
// 全屏障,等价 volatile 读写
VarHandle.fullFence();
// store 屏障:防止前面 store 重排到后面 store
VarHandle.storeStoreFence();
// load 屏障:罕见
VarHandle.loadLoadFence();
2
3
4
5
6
7
8
9
10
11
12
13
14
Disruptor 风格的发布/订阅:
public class DisruptorRingBuffer {
private long sequence = -1; // Plain
private final Object[] data;
// 生产者发布
public void publish(int slot, Object value) {
data[slot] = value;
VarHandle.releaseFence(); // ★ 保证 data 写入早于 sequence 更新可见
sequence = slot; // 此处用 SEQUENCE.setRelease 更佳
}
// 消费者读取
public Object consume(int expected) {
if (sequence < expected) return null;
VarHandle.acquireFence(); // ★ 保证看到 sequence 之后再读 data
return data[expected];
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 6. 性能实证
# 6.1 JMH 三方对比
测试环境:JDK 21 + G1,调用 Math.max(int, int) 1 亿次:
Benchmark Mode Cnt Score Error Units
direct avgt 10 1.823 ± 0.042 ns/op ← baseline
methodHandleInvokeExact avgt 10 2.117 ± 0.067 ns/op ← 1.16×
methodHandleInvoke avgt 10 8.452 ± 0.213 ns/op ← 4.6×
reflectionCached avgt 10 12.631 ± 0.305 ns/op ← 6.9×
reflectionUncached avgt 10 185.421 ± 4.281 ns/op ← 102×
2
3
4
5
6
关键结论:
✅ MethodHandle.invokeExact ≈ 直接调用 (差距 < 20%)
⚠ MethodHandle.invoke 慢一档 (asType 转换开销)
⚠ 缓存的反射慢 7 倍 (Inflation + setAccessible 检查)
❌ 未缓存反射慢 100 倍 (查找 + 校验 + 调用)
2
3
4
# 6.2 invokeExact 才是性能之王
为什么 invokeExact 这么快?
JIT 编译期:
① invokeExact 是多态签名,签名由调用点决定
② JIT 知道目标 MethodHandle 的具体类型
③ JIT 把 invokeExact 内联成对应字节码(如 invokestatic)
④ 内联后被去优化保护(类型变化时去优化)
→ 稳定后字节码与直接调用几乎一致
2
3
4
5
6
7
对比反射:
Method.invoke 的开销链:
① 检查 setAccessible 状态
② 检查参数类型(数组装箱/拆箱)
③ 装箱整型参数 → Integer[]
④ 通过 NativeMethodAccessorImpl / GeneratedMethodAccessor 调用
⑤ 拆箱返回值
→ 即使 Inflation 优化(高频调用切到 GeneratedMethodAccessor),
仍比 invokeExact 慢 5-7 倍
2
3
4
5
6
7
8
9
# 6.3 字段访问的性能阶梯
字段读写的性能(10 亿次 long 读取):
direct field access : 0.51 ns/op 1×
VarHandle.get (plain) : 0.53 ns/op 1.04×
VarHandle.getOpaque : 0.55 ns/op 1.08×
VarHandle.getAcquire : 0.69 ns/op 1.35×
VarHandle.getVolatile : 0.94 ns/op 1.84×
Unsafe.getLong (plain) : 0.55 ns/op 1.08×
Unsafe.getLongVolatile : 0.93 ns/op 1.83×
Field.get (cached, setAccessible) : 5.31 ns/op 10.4×
2
3
4
5
6
7
8
结论:
- ✅ VarHandle plain 与直接访问差距 < 5%
- ✅ VarHandle 与 Unsafe 性能基本持平
- ✅ 反射比 VarHandle 慢 10 倍以上
# 6.4 性能差异的根因
反射:解释执行 + 反射框架开销
Method.invoke()
↓
MethodAccessor.invoke()
↓
NativeMethodAccessorImpl (前 N 次)
GeneratedMethodAccessor (后续,字节码生成)
↓
反射不变量检查 + 装箱 + JNI 调用
MethodHandle:JIT 内联 + intrinsic
invokeExact()
↓
JIT 看到具体 MethodHandle 类型
↓
内联为对应的 invokestatic / invokevirtual
↓
与直接调用相同字节码
VarHandle:JIT intrinsic
getVolatile()
↓
JIT 直接生成 volatile 读字节码 + memory barrier
↓
与 volatile 字段访问性能一致
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 7. 实战案例
# 7.1 高性能动态字段读取器
场景:序列化框架需要按字段名读取对象字段——传统反射太慢,用 MethodHandle 提速 5 倍:
public class FastFieldReader {
// 字段名 → MethodHandle 缓存
private final Map<String, MethodHandle> getters = new ConcurrentHashMap<>();
private final Class<?> targetClass;
public FastFieldReader(Class<?> clazz) throws Exception {
this.targetClass = clazz;
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(clazz, MethodHandles.lookup());
for (Field field : clazz.getDeclaredFields()) {
field.setAccessible(true);
// ★ 一次性构建 MethodHandle,后续直接 invokeExact
MethodHandle mh = lookup.unreflectGetter(field)
.asType(MethodType.methodType(Object.class, Object.class)); // 统一签名为 (Object)Object
getters.put(field.getName(), mh);
}
}
public Object read(Object instance, String fieldName) throws Throwable {
MethodHandle mh = getters.get(fieldName);
if (mh == null) throw new IllegalArgumentException("no such field: " + fieldName);
return mh.invokeExact(instance); // ★ invokeExact 性能接近直接访问
}
}
// 性能对比(百万次读取):
// FastFieldReader.read : 18 ms ★
// Field.get (cached) : 95 ms ← 5× 慢
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 7.2 手撕 Switch 模式底层
复刻一个迷你版 SwitchBootstraps,理解 indy 引导机制:
public class MiniSwitch {
/** 自定义 BootstrapMethod,签名严格遵守 indy 约定 */
public static CallSite typeSwitch(
MethodHandles.Lookup lookup,
String name,
MethodType type,
Object... labels // labels 是 Class<?>[]
) throws Throwable {
// 实际目标:根据 obj 类型返回索引
MethodHandle target = lookup.findStatic(
MiniSwitch.class, "doMatch",
MethodType.methodType(int.class, Object.class, Object[].class));
// 把 labels 数组绑定为第二个参数
target = MethodHandles.insertArguments(target, 1, (Object) labels);
// 返回常量调用点
return new ConstantCallSite(target.asType(type));
}
public static int doMatch(Object obj, Object[] labels) {
if (obj == null) return -1;
for (int i = 0; i < labels.length; i++) {
if (((Class<?>) labels[i]).isInstance(obj)) return i;
}
return -1;
}
}
// 该 BootstrapMethod 可被 indy 字节码引用
// JDK 21 的 SwitchBootstraps.typeSwitch 实现思路与此一致,
// 区别仅在于:使用 ClassValue 缓存 + 处理 enum / sealed 等更多 case 形态
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 7.3 无锁原子计数器
使用 VarHandle 替代 Unsafe 实现高性能计数器:
public class FastCounter {
private static final VarHandle VALUE;
static {
try {
VALUE = MethodHandles.lookup()
.findVarHandle(FastCounter.class, "value", long.class);
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
@SuppressWarnings("unused") // 通过 VarHandle 访问
private volatile long value;
/** 高竞争场景:weakCompareAndSet 减少 CAS 失败重试 */
public long incrementAndGet() {
long current;
long next;
do {
current = (long) VALUE.getVolatile(this);
next = current + 1;
} while (!VALUE.weakCompareAndSet(this, current, next));
return next;
}
/** 低竞争场景:直接 getAndAdd 性能更优 */
public long fastIncrement() {
return (long) VALUE.getAndAdd(this, 1L) + 1;
}
public long get() {
return (long) VALUE.getVolatile(this);
}
}
// 与 AtomicLong 性能对比(单线程,1 亿次自增):
// AtomicLong.incrementAndGet : 1.92 ns/op
// FastCounter.fastIncrement : 1.93 ns/op ★ 等价(AtomicLong 内部就是 VarHandle)
//
// JDK 9+ 后,AtomicInteger / AtomicLong 全部改用 VarHandle 实现,Unsafe 已淡出
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 8. 使用边界与陷阱
# 8.1 invokeExact 类型严格匹配
最常见的错误:
MethodHandle mh = lookup.findStatic(Math.class, "max",
MethodType.methodType(int.class, int.class, int.class));
// ❌ 错误 1:返回值未强转
Object r1 = mh.invokeExact(3, 5); // WrongMethodTypeException
// 调用点签名变成 (int,int)Object,与 mh 不符
// ❌ 错误 2:参数类型不匹配
int r2 = (int) mh.invokeExact(3L, 5); // long 不能直接当 int
// ✅ 正确写法
int r3 = (int) mh.invokeExact(3, 5);
2
3
4
5
6
7
8
9
10
11
12
铁律:调用 invokeExact 时,强转返回值 + 参数类型 100% 严格匹配 MethodType。
# 8.2 Lookup 模块封装
JDK 9 模块化后,Lookup 不能跨模块"白嫖"私有访问:
// ❌ 在模块 A 里查找模块 B 的私有成员
MethodHandles.Lookup lookup = MethodHandles.lookup(); // A 模块的 lookup
lookup.findVirtual(B.class, "privateMethod", ...);
// → IllegalAccessException
// ✅ 正确做法:B 模块在 module-info 中 opens
// module B { opens com.b.internal to A; }
// ✅ 或使用 privateLookupIn(要求目标模块允许)
MethodHandles.Lookup privLookup = MethodHandles.privateLookupIn(B.class, lookup);
2
3
4
5
6
7
8
9
10
# 8.3 VarHandle 必须 static final
性能关键:VarHandle 必须声明为 static final,否则 JIT 无法识别为 intrinsic:
public class Wrong {
private VarHandle handle; // ❌ 非 static final,性能退化到反射级别
public Wrong() throws Exception {
handle = MethodHandles.lookup().findVarHandle(...);
}
}
public class Right {
private static final VarHandle HANDLE; // ✅ JIT 可特化
static {
try {
HANDLE = MethodHandles.lookup().findVarHandle(...);
} catch (Exception e) { throw new ExceptionInInitializerError(e); }
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 8.4 反模式清单
| 反模式 | 问题 | 正确做法 |
|---|---|---|
| 用 invoke 替代 invokeExact | 性能损失 4 倍以上 | 性能敏感路径用 invokeExact |
| MethodHandle 不缓存 | 每次 lookup 重建 | 用 static final 缓存 |
| VarHandle 非 static final | JIT 无法 intrinsic | 必须 static final |
| 用 setVolatile 替代 set | 性能损失 2 倍 | 弱可见时用 plain/opaque |
| MethodHandle 当反射临时替代 | 没用上 invokeExact 优势 | 真热点才用,冷路径反射够了 |
| 跨模块直接 findVirtual | IllegalAccessException | privateLookupIn 或 opens |
| 用 Unsafe 替代 VarHandle | JDK 24+ 风险 | 全部迁移到 VarHandle |
| BootstrapMethod 写复杂逻辑 | 首次调用慢 | 引导方法只做一次性绑定 |
# 9. 综合案例串讲
# 9.1 双案例真相揭晓
① §1.1 反射热路径 35% CPU 真相:缓存的 Method.invoke 仍然慢 20 倍——根因不是查找开销,而是装箱 + 反射框架不变量检查 + JNI 调用的固有成本(§6.4)。即使 JVM Inflation 优化把高频调用切到 GeneratedMethodAccessor,方法调用链仍比直接调用多 4~5 层。正解:用 MethodHandle.invokeExact 替代——JIT 内联后字节码与直接调用一致,性能差距 < 20%(§6.1)。
② §1.2 Unsafe 退场恐慌真相:Unsafe 的 4 大原罪让它注定退场(§5.1)——JDK 24 已计划全面禁用 Unsafe 的 memory access API。官方继任者是 VarHandle——不仅功能等价,还提供更精细的 4 级内存语义控制(§5.2),且 JDK 9 起 AtomicInteger/Long、ConcurrentHashMap 等核心库已全部迁移到 VarHandle,性能与 Unsafe 持平(§6.3)。
③ 7 大追问全部作答:
| 追问 | 答案 | 章节 |
|---|---|---|
| ① MethodHandle 比反射快多少 | invokeExact 接近直接调用,比反射快 5-7 倍 | §6.1、§6.4 |
| ② MethodType 与 invokeExact 严格性 | 多态签名 + 编译期严格匹配 | §3.1、§3.3 |
| ③ MethodHandle 与 indy 关系 | indy 的调用目标就是 MethodHandle | §4 |
| ④ VarHandle 替代 Unsafe 凭什么 | 公开 API + 4 级内存语义 + JIT intrinsic | §5 |
| ⑤ 四级内存语义区别 | plain/opaque/acquire-release/volatile 由弱到强 | §5.2 |
| ⑥ JMH 性能实测 | invokeExact 1.16× / 缓存反射 7× / 未缓存反射 100× | §6.1 |
| ⑦ 实战落地 | 字段读取器/SwitchBootstraps/无锁计数器 | §7 |
# 9.2 一次句柄调用的旅行
把"mh.invokeExact(target, arg)"从源码到 CPU 执行的完整生命线串起来:
T 0 源码:
MethodHandle mh = lookup.findVirtual(String.class, "length",
MethodType.methodType(int.class));
int len = (int) mh.invokeExact("hello");
T+0 编译期:
[§3.3] javac 看到 invokeExact,生成多态签名调用点
[§4.2] indy 字节码? 不!invokeExact 是 invokevirtual,但带 polymorphic 标记
[§3.1] MethodType.methodType(int.class) 在常量池中驻留
T+ 5ns 类加载:
[§3.2] lookup.findVirtual 查找 String.length 方法
[§3] 创建 MethodHandle 实例(DirectMethodHandle.SimpleVirtual)
T+1μs 首次 invokeExact 调用:
[§3.3] 检查 caller 类型签名 == mh.type()
[§3] 反射式查找 + 校验,生成调用 stub
[§3.4] MethodHandle 链路:DirectMethodHandle → invokeBasic → 目标方法
T+10μs JIT 介入(C1):
[14篇] hot 调用点被 JIT 编译
[§6.4] JIT 看到 mh 是 final 字段(JIT 可常量折叠)
[§6.4] JIT 把 invokeExact 内联为直接的 invokevirtual length
T+1ms JIT 完成(C2):
[14篇] C2 进一步内联 length 方法本体
[01篇] String 的 char[]/byte[] 直接读取
最终机器码:1 条 movzx + 1 条 ret
T+50ms 稳定状态:
[§6.1] 实测 2.1 ns/op,与直接调用 1.8 ns/op 差距 < 20%
若类型变化(mh 字段被改)→ 去优化(§14篇)→ 重新编译
跨篇引用全景:
[07篇] 反射 → MethodHandle 是反射的现代继任者
[13篇] 字节码 → invokevirtual / invokedynamic
[14篇] JIT → invokeExact 被特化内联
[25/26篇] 注解处理 → MethodHandle 不依赖反射框架
[27篇] Lambda → invokedynamic + LambdaMetafactory
[30篇] Switch 模式 → invokedynamic + SwitchBootstraps
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 9.3 设计哲学回扣
收官提炼三条工程哲学:
"运行时句柄"是动态语言基础设施的灵魂:MethodHandle/VarHandle 表面上看是"反射的现代替代",但本质是 JVM 为支持动态语言(JRuby/Groovy/Kotlin/Scala)而打造的通用调用基础设施。Lambda、Stream、Switch 模式、字符串拼接、Record 自动 equals——所有这些 Java 8 之后的语言特性,底层全部基于 MethodHandle + invokedynamic。这告诉我们:好的语言演进不靠"特殊化"——它靠把基础设施做强,让新特性自然涌现。当你看到 30 篇的
switch (event),应当意识到那不是 30 个 if-else 的语法糖,而是 SwitchBootstraps + ConstantCallSite + MethodHandle 链路的精心编排。"性能 = 接近原生"才是真本事:反射慢是因为它多了一层框架——参数装箱、类型校验、JNI 跳转、Inflation 切换。而 MethodHandle/VarHandle 的设计哲学是——让 JIT 能把它们识别为 intrinsic,进而内联为对应的字节码指令。invokeExact 严格类型匹配看似刁难,实则是为 JIT 提供"我就是这个签名"的强保证;VarHandle 必须 static final 看似限制,实则是让 JIT 能做常量折叠。API 的"严格"换来的是"零开销抽象"——这是从 C++ 到 Rust 一脉相承的工程美学,Java 也终于站到了这个高度。
"内存语义显式化"是并发编程的成熟标志:从 09 篇的 volatile/JMM 到本篇的 4 级访问模式——Java 并发设计的演进路径是**"先把规则写清楚,再让程序员选择强度"**。早年只有 plain 和 volatile 两档,开发者要么"什么都不保证"要么"全屏障 + 性能损失";如今 plain → opaque → acquire/release → volatile 让你按需付费——保证最弱、性能最高的那一档。这与 C++11 atomic 的
memory_order系列设计哲学完全一致。当语言开始让你"显式选择内存语义",意味着这门语言的并发模型成熟了——你不再是"靠经验和迷信"写并发代码,而是"按规约和测量"写并发代码。这是从匠人到工程师的分水岭。
# 9.4 速查表
MethodHandle 速查:
// 1. 拿 Lookup
MethodHandles.Lookup lookup = MethodHandles.lookup();
// 2. 构建 MethodType
MethodType mt = MethodType.methodType(returnType, paramTypes...);
// 3. 五大查找
lookup.findStatic(C.class, "name", mt) // 静态方法
lookup.findVirtual(C.class, "name", mt) // 虚方法
lookup.findSpecial(C.class, "name", mt, c) // super/private
lookup.findConstructor(C.class, mt) // 构造器
lookup.findGetter(C.class, "field", T.class) // 字段读
// 4. 调用(性能敏感用 invokeExact)
int r = (int) mh.invokeExact(args...);
// 5. 句柄变换
mh.asType(newType) // 类型适配
mh.bindTo(receiver) // 绑定接收者
MethodHandles.insertArguments(mh, pos, vals) // 插入参数
MethodHandles.filterArguments(mh, pos, filters) // 参数预处理
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
VarHandle 速查:
// 必须 static final
private static final VarHandle X = MethodHandles.lookup()
.findVarHandle(C.class, "field", T.class);
// 4 级访问
X.get(this) / X.set(this, v) // plain
X.getOpaque(this) / X.setOpaque(this, v) // opaque
X.getAcquire(this) / X.setRelease(this, v) // acquire-release
X.getVolatile(this) / X.setVolatile(this, v) // volatile
// CAS 家族
X.compareAndSet(this, e, u) // 强 CAS
X.weakCompareAndSet(this, e, u) // 弱 CAS(循环中用)
X.compareAndExchange(this, e, u) // 失败返回当前值
X.getAndAdd(this, delta) // 原子加
X.getAndSet(this, v) // 原子换
// 屏障(罕用)
VarHandle.acquireFence();
VarHandle.releaseFence();
VarHandle.fullFence();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
性能选择口诀:
方法调用:
① 编译期已知 → 直接调用
② 运行期已知 + 性能敏感 → MethodHandle.invokeExact
③ 运行期已知 + 一次性 → 反射也行
④ 完全动态(语言互操作)→ invokedynamic + BootstrapMethod
字段访问:
① 编译期已知 → 直接 get/set
② 跨线程可见性 → VarHandle.getVolatile / setVolatile
③ Disruptor 风格高性能发布 → getAcquire / setRelease
④ 原子更新 → VarHandle.compareAndSet / getAndAdd
⑤ Unsafe 全部场景 → VarHandle 完全替代
2
3
4
5
6
7
8
9
10
11
12
下一篇进入 卷四第 32 篇:ASM/Javassist/ByteBuddy 字节码框架对比——承接本篇 LambdaMetafactory / SwitchBootstraps "运行时生成代码"的钩子,把"字节码增强"三大主流框架完整对比:从 ASM 的 visitor 模式 + 性能极限、Javassist 的源码层 API + 上手友好、到 ByteBuddy 的 DSL 链式 + Spring 6 默认选型——附手撕一个简易 Mock 框架的完整代码,帮助你理解 Mockito/JaCoCo/Skywalking 等中间件背后的"字节码黑魔法"。