varhandle 是 java 9 引入的、用于安全替代 unsafe 的标准化变量操作机制,具备类型检查、访问控制和 jit 优化优势,避免了 unsafe 因字段重排序或偏移量计算导致的运行时错误。

VarHandle 是什么,为什么不用 Unsafe 了
VarHandle 是 Java 9 引入的、用于安全替代 Unsafe 直接内存操作的标准化机制。它不是语法糖,而是 JVM 层面支持的、带类型检查和访问控制的变量操作入口。比起手写 Unsafe 调用,它能避免 IllegalArgumentException(比如字段不可访问)、UnsupportedOperationException(比如静态字段在非静态上下文中误用),还能被 JIT 更好地内联优化。
常见错误现象:用 Unsafe 手动计算偏移量时,字段重排序或类加载顺序变化导致 offset 错误;而 VarHandle 在查找阶段就绑定字段,运行时只做校验,更稳。
- 必须通过
MethodHandles.lookup()获取查找器,且该查找器需有目标类的访问权限(否则抛IllegalAccessException) - 静态字段要用
findStaticVarHandle(),实例字段用findVarHandle(),别混用 - 泛型类字段(如
List<string> field</string>)的 VarHandle 类型是VarHandle,但它的get()/set()方法签名仍按擦除后类型处理(即Object)
怎么获取一个靠谱的 VarHandle(以实例字段为例)
核心是两步:拿到有权限的 MethodHandles.Lookup,再调用对应查找方法。不能直接 new,也不能跨类复用 lookup 实例(除非显式开放权限)。
使用场景:你想原子更新某个对象的 int counter 字段,又不想加锁。
立即学习“Java免费学习笔记(深入)”;
class Counter {
int value;
}
// ✅ 正确写法
MethodHandles.Lookup lookup = MethodHandles.lookup();
VarHandle vh = lookup.findVarHandle(Counter.class, "value", int.class);
Counter c = new Counter();
vh.set(c, 42); // 原子写入
int x = (int) vh.get(c); // 原子读取
- 字段名和类型必须严格匹配,大小写敏感,类型不接受自动装箱(
int.class≠Integer.class) - 如果字段是
private,lookup 必须来自定义该字段的类内部(否则抛IllegalAccessException) - 嵌套类字段要写全路径,比如
Outer$Inner.class,不能只写Inner.class
VarHandle 的内存语义怎么选(get/set vs getAcquire/setRelease)
VarHandle 默认的 get()/set() 是 volatile 语义(相当于 volatile 字段读写),但你也可以显式指定更轻量或更强的语义——这直接影响性能和线程可见性。
性能影响:在 x86 上,getAcquire 和普通 get 汇编指令一样(无额外 fence),但 getVolatile 会插入 lock addl $0x0,(%rsp);ARM 上差异更大。
-
get()/set():等价于volatile读/写,最常用 -
getAcquire()/setRelease():适合“发布-消费”模式(如初始化后设标志位),比 volatile 轻,但不保证全局有序 -
getOpaque()/setOpaque():仅禁止重排序,不提供跨线程可见性,慎用(JDK 内部用得多,业务代码极少需要) - 没有
compareAndSet()的变体?有,叫compareAndSet(),但参数顺序是(obj, expected, desired),别写反
哪些地方 VarHandle 会默默失败或行为异常
它不会报错,但结果可能不符合直觉——尤其在反射、泛型、继承场景下。
容易踩的坑:
- 对 final 字段获取 VarHandle 成功,但
set()会抛UnsupportedOperationException(即使字段在构造器里已赋值) - 数组元素的 VarHandle(
arrayElementVarHandle())返回的是VarHandle,但它的get()第二个参数是int index,不是long;越界不抛ArrayIndexOutOfBoundsException,而是由 JVM 抛出(位置不可控) - 通过子类 Class 获取父类字段的 VarHandle 可能成功,但运行时
get()会抛IllegalAccessError(JVM 检查实际调用者,不是声明类) - 模块系统下,如果目标类在未导出的包里(如
java.base内部类),lookup.findVarHandle()直接失败,连IllegalAccessException都不抛,而是NoSuchFieldException
复杂点在于:VarHandle 的创建是静态绑定,但它的访问控制检查是动态的,且发生在每次调用时。这意味着,同一个 VarHandle 在不同类加载器或不同模块上下文中,可能一次成功、一次失败。










