sun.misc.Unsafe 是什么?它有哪些危险的能力?为什么叫「Unsafe」?
简化版
sun.misc.Unsafe 是 JDK 内部的一个「后门类」——它提供了一批「绕过 JVM 安全检查、直接操作底层内存和对象」的能力,因为「不安全」(用错会导致 JVM 崩溃、内存泄漏)所以叫 Unsafe。它的主要能力:① CAS 操作(compareAndSwapInt/Long/Object,是 AQS、原子类的底层);② 直接操作内存(分配/释放堆外内存 allocateMemory、直接读写内存地址,绕过 JVM 的数组越界检查等);③ 直接操作对象字段(通过内存偏移量读写字段,绕过访问权限);④ 直接创建对象(allocateInstance 不调用构造器就创建对象);⑤ 线程挂起/唤醒(park/unpark,是 LockSupport 的底层)。它是很多 JUC 类、Netty、序列化框架的底层基础,但普通开发者不应该直接用它(危险、且是内部 API,未来可能被移除)。
详细版
Unsafe 的核心能力:
| 能力 | 方法 | 用在哪 |
|---|---|---|
| CAS | compareAndSwapInt/Long/Object | AQS、AtomicInteger 等原子类 |
| 堆外内存 | allocateMemory/freeMemory/putXxx/getXxx | DirectByteBuffer、Netty |
| 对象字段操作 | objectFieldOffset/putObject/getObject | 序列化框架、反射优化 |
| 直接创建对象 | allocateInstance(不调构造器) | 序列化(跳过构造器反序列化)、框架 |
| 线程挂起/唤醒 | park/unpark | LockSupport、AQS |
| 内存屏障 | loadFence/storeFence/fullFence | 并发框架 |
// Unsafe 的构造器是私有的、且 getUnsafe() 会检查调用者是否是启动类加载器加载的
// 普通代码要通过反射获取(本身就说明"不该随便用")
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
// CAS(原子类的底层)
unsafe.compareAndSwapInt(obj, offset, expected, newValue);
// 堆外内存(DirectByteBuffer 的底层)
long address = unsafe.allocateMemory(1024); // 分配 1KB 堆外内存
unsafe.putLong(address, 123L); // 直接写内存地址
unsafe.freeMemory(address); // 手动释放(忘了就内存泄漏!)
// 不调构造器创建对象(序列化反序列化用)
Object obj = unsafe.allocateInstance(SomeClass.class); // 绕过构造器
⚠️ 为什么叫「Unsafe」(不安全)——因为它绕过了 JVM 的所有安全保护:① 直接操作内存(可以写任意内存地址,写错了直接 JVM 崩溃/段错误);② 堆外内存要手动释放(忘了 freeMemory 就内存泄漏,且不受 GC 管理);③ 绕过类型检查、访问权限、数组越界检查(可能破坏对象状态、访问私有字段)。所以它是「给 JDK 内部和框架用的后门」,普通开发者不该直接用——用错的后果是 JVM 级别的崩溃,不是普通的异常。
完整版教学
一、Unsafe 是什么:JVM 的后门
sun.misc.Unsafe 是 JDK 提供的一个「能绕过 JVM 安全机制、直接操作底层」的类——它是 Java 这个「安全语言」里的一个「后门」:
Java 的设计是"安全"的:
- 自动内存管理(GC,不用手动 malloc/free)
- 类型安全(不能乱转类型)
- 数组越界检查、访问权限检查
- 不能直接操作内存地址
但有时(尤其框架、JUC 底层)需要"绕过这些安全机制"做底层操作:
- 需要 CAS(无锁并发)
- 需要直接操作内存(堆外内存、高性能)
- 需要绕过构造器创建对象(序列化)
→ Unsafe 提供了这些"不安全但强大"的能力
Unsafe 的定位是「为 JDK 内部和高性能框架提供底层操作能力的后门」——它牺牲「安全」换「能力」。之所以叫「Unsafe」,是 JDK 在明确警告「这些操作是不安全的、用错会出严重问题、不是给普通开发者用的」。它是很多「看起来很神奇」的功能(原子类的 CAS、堆外内存、序列化跳过构造器)的底层实现。理解「Unsafe 是绕过 JVM 安全机制的后门、给 JDK 内部和框架用、牺牲安全换能力」,就理解了它的定位——它是「危险但强大的底层工具」。
二、CAS:原子类和 AQS 的底层
Unsafe 最重要的能力是 CAS(Compare-And-Swap)——它是整个 JUC(原子类、AQS、锁)的底层基石:
Unsafe 提供 CAS 方法:
compareAndSwapInt(对象, 字段偏移量, 期望值, 新值)
→ 原子地:如果字段当前值等于期望值,就改成新值,返回是否成功
→ 底层对应 CPU 的 cmpxchg 指令(硬件保证原子性)
用在哪:
AtomicInteger.incrementAndGet() → 底层调 unsafe.compareAndSwapInt(自旋 CAS)
AQS 的 state 更新 → unsafe.compareAndSwapInt
→ 所有"无锁并发"都建立在 Unsafe 的 CAS 上
前面「CAS」题讲过 CAS 的原理,这里补充「CAS 的能力来自 Unsafe」——AtomicInteger、AQS 等的 CAS 操作,最终都是调用 Unsafe.compareAndSwapInt/Long/Object(它对应 CPU 的原子指令)。所以「Java 的无锁并发(原子类、AQS、ConcurrentHashMap)能实现,底层靠的是 Unsafe 的 CAS」。JDK 9 后新增了 VarHandle 作为 Unsafe CAS 的「安全替代」(提供 CAS 能力但更受控),原子类内部逐渐改用 VarHandle。理解「Unsafe 的 CAS 是原子类/AQS/无锁并发的底层、对应 CPU 原子指令、JDK9 后有 VarHandle 替代」,就理解了 Unsafe 在并发领域的核心作用——它是无锁并发的地基。
三、堆外内存:DirectByteBuffer 和 Netty 的底层
Unsafe 能「直接分配和操作堆外内存」——这是 DirectByteBuffer、Netty 的底层:
Unsafe 的堆外内存操作:
allocateMemory(size):分配一块堆外内存(不在 JVM 堆里、不受 GC 管理),返回内存地址
putLong(address, value) / getLong(address):直接读写内存地址
freeMemory(address):手动释放堆外内存(★ 忘了释放就内存泄漏)
用在哪:
DirectByteBuffer(NIO 的堆外缓冲区)→ 底层用 Unsafe.allocateMemory
Netty 的堆外内存池 → 基于 Unsafe
→ 高性能 IO、零拷贝 都用堆外内存
为什么用堆外内存:
① 减少 GC 压力(不在堆里、GC 不管它)
② 零拷贝(IO 时不用在堆和内核之间多复制一次)
③ 但要手动管理(分配/释放),忘了 free 就泄漏
Unsafe 的堆外内存能力让「高性能 IO」成为可能——DirectByteBuffer 用它分配堆外缓冲区(前面 io、netty 题讲过堆外内存的零拷贝优势)。堆外内存「不受 GC 管理」是双刃剑:好处是减少 GC 压力、支持零拷贝;坏处是要手动释放(freeMemory),忘了就泄漏(DirectByteBuffer 用 Cleaner(虚引用)来自动释放,但仍可能泄漏——见「堆外内存」题)。理解「Unsafe 能直接操作堆外内存(allocateMemory/freeMemory)、是 DirectByteBuffer/Netty 的底层、堆外不受 GC 管理要手动释放」,就理解了 Unsafe 在高性能 IO 领域的作用。
四、直接操作对象:绕过构造器和访问权限
Unsafe 还能「绕过 JVM 的封装,直接操作对象」——包括不调构造器创建对象、直接读写字段:
① 不调构造器创建对象:
allocateInstance(Class):分配一个对象,但不调用它的构造器!
→ 序列化/反序列化时用(反序列化要"重建对象"但不该调构造器,
因为构造器可能有副作用/校验;Unsafe 能"凭空"造一个对象再填字段)
② 直接读写对象字段(通过内存偏移量):
objectFieldOffset(field):获取字段在对象里的内存偏移量
putObject(obj, offset, value) / getObject:直接按偏移量读写字段
→ 绕过 getter/setter、绕过访问权限(private 也能改)
→ 序列化框架、高性能反射用(比反射快)
这些能力体现了 Unsafe「绕过封装」的危险性:① allocateInstance 不调构造器造对象——序列化框架用它「重建对象」(反序列化不该调构造器,否则构造器的副作用/校验会干扰),但它破坏了「对象必须通过构造器创建」的规则;② 直接按偏移量读写字段——绕过 getter/setter 和访问权限(能改 private 字段),序列化和高性能框架用它(比反射快),但破坏了封装。这些能力强大但危险(破坏对象的正常创建和封装)。理解「Unsafe 能不调构造器造对象(序列化用)、按偏移量直接读写字段(绕过封装、比反射快)」,就理解了它「绕过封装」的能力——强大但危险。
五、park/unpark:线程阻塞的底层
Unsafe 还提供「线程的挂起(park)和唤醒(unpark)」——这是 LockSupport 和 AQS 阻塞线程的底层:
Unsafe 的线程阻塞:
park():挂起当前线程(阻塞它)
unpark(thread):唤醒指定线程
用在哪:
LockSupport.park()/unpark() → 底层调 Unsafe.park/unpark
AQS 阻塞获取不到锁的线程 → 用 LockSupport.park(底层 Unsafe)
→ 所有"线程阻塞等待"(锁、并发工具)都用它
park/unpark 的特点(相比 wait/notify):
① 不需要在 synchronized 块里(更灵活)
② unpark 可以"先于"park 调用(有一个许可,不会丢唤醒)
Unsafe.park/unpark 是 Java 阻塞线程的底层机制——LockSupport(前面提过)封装了它,AQS 用 LockSupport.park 阻塞获取锁失败的线程。它比 wait/notify 灵活(不用在 synchronized 里、unpark 可以先于 park 调用不会丢唤醒)。所以「Java 的锁和并发工具阻塞/唤醒线程,底层是 Unsafe 的 park/unpark」。理解「Unsafe 的 park/unpark 是 LockSupport/AQS 阻塞唤醒线程的底层、比 wait/notify 灵活」,就理解了 Unsafe 在线程阻塞领域的作用——它是并发工具阻塞线程的地基。
六、为什么普通开发者不该用
Unsafe 强大,但普通开发者不该直接用它,原因很充分:
不该用 Unsafe 的原因:
① 危险:直接操作内存,写错地址 → JVM 崩溃(不是异常,是段错误级别的崩溃)
② 内存泄漏:堆外内存要手动 free,忘了就泄漏(且 GC 管不了)
③ 破坏封装和安全:绕过访问权限、类型检查,可能破坏对象状态
④ 是内部 API:sun.misc.Unsafe 是 JDK 内部类,不保证兼容
- JDK 9 模块化后,访问它更困难(需要 --add-opens)
- 未来版本可能移除/限制(有替代方案 VarHandle/MemorySegment)
正确的替代(如果确实需要底层能力):
CAS → VarHandle(JDK 9+,安全的 CAS)
堆外内存 → ByteBuffer.allocateDirect 或 Foreign Memory API(JDK 14+ 的 MemorySegment)
→ 用官方的、受控的 API,别直接用 Unsafe
核心:Unsafe 是「给 JDK 内部和底层框架用的后门」,普通业务开发几乎永远不该直接用它——它太危险(JVM 崩溃、内存泄漏、破坏安全),而且是内部 API(不保证兼容、JDK 9+ 访问受限、未来可能移除)。如果确实需要底层能力,用官方的替代:CAS 用 VarHandle(JDK 9+)、堆外内存用 ByteBuffer.allocateDirect 或 Foreign Memory API(JDK 14+ 的 MemorySegment)。理解「普通开发者不该用 Unsafe(危险+内部API)、有 VarHandle/MemorySegment 等官方替代」,就掌握了对待 Unsafe 的正确态度——「知道它的存在和作用,但不直接用」。
记忆钩子:「Unsafe 是 JDK 内部后门类,绕过 JVM 安全机制直接操作底层(所以叫 unsafe):CAS(原子类/AQS 底层)、堆外内存 allocateMemory/freeMemory(DirectByteBuffer/Netty 底层,要手动释放)、直接操作对象(allocateInstance 不调构造器造对象/按偏移量读写字段绕过封装,序列化用)、park/unpark(LockSupport/AQS 阻塞唤醒底层);危险(写错内存 JVM 崩溃/堆外泄漏/破坏封装)、是内部 API;普通开发者别直接用,替代:CAS 用 VarHandle、堆外用 MemorySegment」。
七、常见误区与追问
- 误区:Unsafe 是普通开发者常用的工具。 它是 JDK 内部/框架用的后门,普通业务开发几乎永远不该直接用——太危险(JVM 崩溃、内存泄漏)、是内部 API(不保证兼容、JDK9+ 访问受限、未来可能移除)。
- 误区:Unsafe 的操作和普通 Java 代码一样安全。 它绕过 JVM 所有安全检查——直接写内存地址写错会 JVM 崩溃(段错误级别,不是异常)、堆外内存忘了 free 会泄漏、绕过访问权限可能破坏对象状态。
- 误区:原子类和 AQS 是纯 Java 实现的。 它们的 CAS 底层调用 Unsafe.compareAndSwapXxx(对应 CPU 原子指令)、AQS 阻塞线程用 Unsafe.park/unpark;无锁并发建立在 Unsafe 之上。
- 误区:Unsafe.allocateInstance 和 new 一样。 allocateInstance 不调用构造器就创建对象(凭空造一个对象、字段是默认值),用于序列化反序列化(不该调构造器);new 会调构造器。
- 追问:Unsafe 为什么叫「不安全」? 它绕过 JVM 的安全机制——直接操作内存(写错地址导致 JVM 崩溃)、堆外内存要手动释放(忘了就泄漏且 GC 管不了)、绕过类型检查/访问权限/数组越界检查(可能破坏对象状态);用错的后果是 JVM 级别的崩溃。
- 追问:Unsafe 有哪些主要能力,分别用在哪? CAS(原子类/AQS)、堆外内存分配释放(DirectByteBuffer/Netty)、不调构造器创建对象和直接读写字段(序列化)、park/unpark 线程阻塞唤醒(LockSupport/AQS)、内存屏障(并发框架)。
- 追问:Unsafe 有官方替代吗? 有——CAS 用 VarHandle(JDK 9+,安全受控的 CAS)、堆外内存用 ByteBuffer.allocateDirect 或 Foreign Function & Memory API 的 MemorySegment(JDK 14+);官方在逐步用这些受控 API 替代 Unsafe。
八、加强记忆
sun.misc.Unsafe 是 JDK 内部的「后门类」——提供「绕过 JVM 安全机制、直接操作底层内存和对象」的能力,因「不安全」(用错导致 JVM 崩溃/内存泄漏)而叫 Unsafe。主要能力:① CAS(compareAndSwapXxx,对应 CPU 原子指令,是原子类/AQS/无锁并发的底层);② 堆外内存(allocateMemory/freeMemory/直接读写地址,是 DirectByteBuffer/Netty 的底层,不受 GC 管理、要手动释放否则泄漏);③ 直接操作对象(allocateInstance 不调构造器创建对象、按内存偏移量直接读写字段绕过封装,序列化框架用它);④ park/unpark(线程挂起/唤醒,是 LockSupport/AQS 阻塞线程的底层);⑤ 内存屏障。它「不安全」是因为绕过了 JVM 的所有保护(写错内存 → JVM 崩溃、堆外忘释放 → 泄漏、绕过类型/权限检查 → 破坏状态)。它是 JUC、Netty、序列化框架的底层基础,但普通开发者不该直接用(危险 + 内部 API,JDK9+ 访问受限、未来可能移除)——官方替代:CAS 用 VarHandle(JDK9+)、堆外用 MemorySegment(JDK14+)。一句话「Unsafe 是绕过 JVM 安全的后门:CAS(原子类底层)、堆外内存(DirectByteBuffer 底层要手动释放)、不调构造器造对象+直接读写字段(序列化)、park/unpark(AQS 底层),危险且内部 API 别直接用,替代 VarHandle/MemorySegment」。