Unsafe是JVM底层机制的公开出口,提供直接内存操作、park/unpark线程调度、CAS原子指令及绕过构造器等能力,支撑Netty零拷贝、AQS锁等高性能组件,但误用会导致JVM崩溃或数据损坏。

Unsafe 类不是用来“绕过限制”的玩具,而是 JVM 底层机制的公开出口。它不破坏规则,而是暴露了规则之下真实运行的那层——内存地址、CPU 原子指令、线程调度原语。用得好,是 Netty 零拷贝、AQS 锁、AtomicInteger 的基石;用错一步,可能直接触发段错误或静默数据损坏。
直接读写任意内存地址
Unsafe 提供了 putXXX 和 getXxx 系列方法(如 putInt、getInt、putObject、getObject),但它们操作的不是 Java 对象字段,而是**裸内存地址**。关键前提是:你得先拿到一个合法的 long 类型地址值。
- 地址通常来自 allocateMemory 分配的堆外内存,例如:
long addr = unsafe.allocateMemory(4); - 也可通过 objectFieldOffset 获取对象字段在内存中的偏移量,再结合对象基址(staticFieldBase 或 arrayBaseOffset)算出绝对地址
- 写入时无需类型检查或访问控制——
unsafe.putInt(addr, 123)直接覆写 4 字节;读取同理,unsafe.getInt(addr)按 int 解析该地址内容 - 风险在于:地址无效会 crash JVM;类型误判(比如把 double 地址当 int 读)导致值错乱;无 GC 管理,必须手动 freeMemory
挂起与唤醒线程(park/unpark)
Unsafe 的 park 和 unpark 是 JVM 线程阻塞机制的底层实现,比 Object.wait/notify 更轻量、更可控,且不依赖 synchronized 块。
-
unsafe.park(false, 0)让当前线程无限期挂起,直到被其他线程 unpark -
unsafe.unpark(thread)唤醒指定线程;若线程尚未 park,则下次 park 会立即返回(即支持“许可预发”) - AQS 就靠这对方法实现等待队列的线程调度:获取锁失败 → park;释放锁并唤醒后继 → unpark
- 注意:park 不释放任何锁,也不参与 monitor 机制;它纯粹是线程状态切换,和操作系统 futex 或 Windows Event 一一对应
CAS 操作:无锁并发的硬件级保障
CAS 不是 Java 层逻辑,而是 Unsafe 将 CPU 的 cmpxchg(x86)或 ldaxr/stlxr(ARM)等原子指令封装成的 Java 方法。它保证“读-判-写”三步不可分割。
- 典型调用:
unsafe.compareAndSwapInt(obj, offset, expected, newValue) - obj 是目标对象,offset 是字段在对象内存布局中的字节偏移(非 public 字段也能改),expected 是期望旧值,newValue 是待写入值
- 成功返回 true,失败返回 false —— 不抛异常,由上层决定重试、降级或放弃
- ABA 问题真实存在:某字段从 A→B→A,CAS 会误判为“未变”。解决方式不是避免 CAS,而是引入版本号(如 AtomicStampedReference)
- 所有 java.util.concurrent.atomic 包下的类,底层都走这一套;没有 Unsafe,就没有真正的无锁结构
绕过构造器与字段访问限制
Unsafe 允许跳过语言层约束,直击运行时本质:
- allocateInstance(Class) 创建对象实例,完全不调用任何构造方法(包括 private 构造器),字段全为默认值(0/null/false)
- putXxx 可修改 final 字段——不是“反射破 final”,而是直接写内存,JVM 不做 final 语义校验
- staticFieldOffset + staticFieldBase 可定位静态字段地址,配合 put/get 实现静态变量原子更新(比 volatile 更底层)
- 这些能力被序列化框架(Kryo)、RPC 库(gRPC Java 版反序列化)、缓存系统(Caffeine 内部对象重建)所依赖,但业务代码几乎不该触碰

















