深入理解 Java Unsafe:从底层魔法到被淘汰的命运
本文系统讲解
sun.misc.Unsafe的设计初衷、核心能力、使用方式、业内典型实践(以 Netty 为例),以及 Oracle JDK 团队对它的逐步淘汰计划。
一、Unsafe 基础介绍
1.1 它是什么
sun.misc.Unsafe 是 JDK 内部提供的一个底层工具类,位于 jdk.unsupported 模块中。它提供了一组 native 方法,允许 Java 代码直接操作内存、执行 CAS 原子指令、调度线程、绕过构造器创建对象等------这些操作在标准 Java API 中是被严格禁止的。
从类名就可以看出 Oracle 的态度:Unsafe = 不安全。它从诞生之初就不是为广大应用开发者设计的公共 API。
1.2 历史与规模
- 2002 年 随 JDK 1.4 引入,最初目的是让 JDK 自身的类库(如
java.util.concurrent、java.nio)能够执行底层操作。 - 截至 JDK 22,
Unsafe共有 87 个方法 ,其中 79 个与内存访问相关(on-heap / off-heap / bimodal)。 - JDK 9 模块系统引入时,绝大多数
sun.*内部 API 被封装(JEP 260),唯独Unsafe因为生态依赖过于广泛而被保留为开箱可用。
1.3 如何获取 Unsafe 实例
Unsafe 的构造器是私有的,官方获取方式是静态方法 getUnsafe():
java
public static Unsafe getUnsafe() {
Class<?> caller = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(caller.getClassLoader()))
throw new SecurityException("Unsafe");
return theUnsafe;
}
它会检查调用者的类加载器------只有由 Bootstrap ClassLoader 加载的类(即 JDK 核心类)才能直接调用 。普通应用代码直接调用会抛 SecurityException。
因此,社区通用的做法是通过反射绕过检查:
java
public static Unsafe getUnsafe() {
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
return (Unsafe) f.get(null);
} catch (Exception e) {
throw new RuntimeException("Failed to obtain Unsafe instance", e);
}
}
从 JDK 9 开始,
setAccessible(true)对java.base模块的字段会受到模块封装限制,需要通过--add-opens java.base/jdk.internal.misc=ALL-UNNAMED等参数放行。这本身就是 Oracle 在逐步收紧 Unsafe 访问的信号。
二、设计 Unsafe 的目的
理解 Unsafe 的设计初衷,关键在于回到 2002 年的技术背景:
2.1 JDK 自身需要一把"瑞士军刀"
2002 年的 Java 平台缺少以下标准能力:
- 无锁原子操作(CAS) :
java.util.concurrent.atomic包直到 JDK 1.5(2004 年)才出现,而 AQS、ConcurrentHashMap 等并发基础组件需要 CAS 来实现。 - 堆外内存操作 :
java.nio.ByteBuffer虽然支持直接内存,但有 2GB 上限,且无法手动精确释放。 - 低级内存屏障:volatile 语义的底层实现需要直接的内存屏障指令。
- 线程挂起/恢复 :
LockSupport.park/unpark的底层实现。
JDK 团队需要一个统一的入口来封装这些 native 操作,于是 Unsafe 应运而生。
2.2 设计假设:仅内部使用,最终会被替代
根据 OpenJDK 官方在 JEP 471 中的回顾,Unsafe 的设计基于三个明确假设:
- 仅限 JDK 内部使用:调用者会在使用前执行详尽的安全检查。
- 不是永久 API:它从未被设想为面向广泛客户端的公共接口。
- 最终会有安全的标准 API 替代:这只是一个过渡方案。
然而,2002 年没有技术手段阻止 Unsafe 被 JDK 之外的代码使用。随着高性能中间件的兴起,Unsafe 逐渐"出圈",成为 Netty、Hadoop、Cassandra、Kafka 等组件的性能基石。
三、Unsafe 的利与弊
3.1 优势:性能与能力的极致释放
| 优势 | 说明 |
|---|---|
| 直接内存操作 | 可以像 C 语言一样分配、释放、读写任意内存地址,不受 JVM 管理 |
| 堆外内存不受 GC 管控 | 分配大块内存不增加 GC 压力,适合缓存、网络缓冲区等场景 |
| 无 2GB 限制 | ByteBuffer.allocateDirect() 受 int 容量限制,Unsafe.allocateMemory(long) 支持 long 级别的超大块 |
| CAS 原子操作 | 直接映射到硬件级别的 lock cmpxchg 指令,是无锁并发的基础 |
| volatile / ordered 语义 | 精确控制内存屏障,比 synchronized 开销小得多 |
| 绕过构造器 | allocateInstance() 可以不调用构造器直接创建对象,在序列化、反序列化、代理生成中有用 |
| 直接修改私有字段 | 通过字段偏移量可以绕过访问权限检查,破坏封装但在框架中有时必要 |
| 线程精确调度 | park/unpark 比 Object.wait/notify 更灵活,是 AQS 的底层基础 |
3.2 弊端:不安全的代价
| 弊端 | 说明 |
|---|---|
| JVM 崩溃 | 非法内存地址读写会直接导致 JVM 进程崩溃(SIGSEGV),而不是抛出 Java 异常 |
| 未定义行为 | 错误的字段偏移量、类型不匹配的写入可能导致对象内存布局损坏,产生难以排查的诡异 Bug |
| 内存泄漏 | 堆外内存不受 GC 管理,忘记 freeMemory() 会导致进程内存持续增长 |
| 破坏封装与安全 | 可以直接修改 private final 字段,绕过访问控制,破坏 Java 的安全模型 |
| 可能禁用 JIT 优化 | 某些 Unsafe 用法会导致 JIT 无法进行边界检查消除等优化,性能反而不如普通数组 |
| 跨平台风险 | 依赖具体 JVM 实现的对象内存布局,不同 JDK 版本/厂商之间可能不一致 |
总结:Unsafe 给了开发者 C 语言级别的能力,也给了开发者 C 语言级别的崩溃风险。
四、核心能力与使用场景
Unsafe 的能力可以分为以下几大类:
4.1 内存操作(Off-heap Memory)
这是 Unsafe 最核心的能力,允许直接在 JVM 堆外分配和操作原生内存:
java
// 分配堆外内存,返回内存地址
long address = unsafe.allocateMemory(1024); // 分配 1024 字节
long newAddress = unsafe.reallocateMemory(address, 2048); // 重新分配
unsafe.setMemory(newAddress, 2048, (byte) 0); // 批量置零
unsafe.freeMemory(newAddress); // 释放内存
典型场景:
- 高性能网络缓冲区(Netty 的直接内存池)
- 大对象缓存(避免 GC 压力)
- 与原生库交互时的内存共享
4.2 对象字段操作(On-heap Memory)
通过字段偏移量直接读写对象内存,绕过访问权限和 setter:
java
class User {
private int age;
private String name;
}
// 获取字段偏移量
long ageOffset = unsafe.objectFieldOffset(User.class.getDeclaredField("age"));
long nameOffset = unsafe.objectFieldOffset(User.class.getDeclaredField("name"));
User user = new User();
// 直接写入私有字段
unsafe.putInt(user, ageOffset, 25);
unsafe.putObject(user, nameOffset, "Alice");
// 直接读取
int age = unsafe.getInt(user, ageOffset);
还支持 volatile 语义和 ordered 写入:
java
unsafe.putIntVolatile(user, ageOffset, 30); // volatile 写,带 StoreStore + StoreLoad 屏障
unsafe.putOrderedInt(user, ageOffset, 30); // ordered 写,仅 StoreStore 屏障,性能更高
int v = unsafe.getIntVolatile(user, ageOffset); // volatile 读
典型场景:
- 并发框架中的状态字段更新
- 序列化/反序列化框架直接操作字段
- 高性能对象池
4.3 CAS 原子操作
Compare-And-Swap 是无锁并发的基石,直接映射到硬件原子指令:
java
// 比较并交换 int,返回是否成功
boolean success = unsafe.compareAndSwapInt(user, ageOffset, expectedValue, newValue);
// 类似的还有 compareAndSwapLong / compareAndSwapObject
CAS 的典型用法是自旋重试:
java
while (!unsafe.compareAndSwapInt(obj, offset, oldValue, newValue)) {
oldValue = unsafe.getIntVolatile(obj, offset); // 重新读取最新值
}
典型场景:
AtomicInteger/AtomicLong等原子类的底层实现- AQS(
AbstractQueuedSynchronizer)的状态更新 ConcurrentHashMap的节点插入和 sizeCtl 控制- 各种无锁数据结构
4.4 线程调度(park / unpark)
java
// 挂起当前线程,isAbsolute=false 表示相对时间(纳秒)
unsafe.park(false, 3_000_000_000L); // 挂起 3 秒
// 恢复指定线程
unsafe.unpark(thread);
park/unpark 与 Object.wait/notify 的关键区别:
- unpark 可以先于 park 调用:相当于给线程一个"许可",后续 park 会立即返回。
- park 响应中断但不抛异常 :通过
Thread.interrupted()检查。 - 更细粒度的超时控制:支持纳秒级相对时间和绝对时间。
典型场景:
LockSupport的底层实现- AQS 中线程的挂起与唤醒
- 各种同步器(
ReentrantLock、Semaphore、CountDownLatch)
4.5 数组操作
java
// 获取数组第一个元素的偏移量
int baseOffset = unsafe.arrayBaseOffset(int[].class); // 通常是 16
// 获取数组元素的字节步长
int indexScale = unsafe.arrayIndexScale(int[].class); // 通常是 4
int[] arr = new int[10];
// 直接读写数组元素,绕过边界检查
unsafe.putInt(arr, baseOffset + 2L * indexScale, 42);
int val = unsafe.getInt(arr, baseOffset + 2L * indexScale);
典型场景:
- 高性能数组操作(跳过边界检查)
- 大数组的批量内存操作
4.6 绕过构造器创建对象
java
User user = (User) unsafe.allocateInstance(User.class);
// user 已创建,但构造器未执行,所有字段为默认值
典型场景:
- 序列化/反序列化框架(Kryo、Hessian)
- 代理对象生成
- 对象池(避免重复执行构造器开销)
4.7 其他能力
addressSize():返回 native 指针大小(4 或 8 字节)pageSize():返回操作系统内存页大小loadFence()/storeFence()/fullFence():内存屏障(JDK 8+)defineClass()/defineAnonymousClass():动态类定义(已在 JDK 11/17 移除)
五、代码示例
5.1 完整示例:基于 Unsafe 的自定义原子整数
java
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class UnsafeAtomicInteger {
private static final Unsafe UNSAFE;
private static final long VALUE_OFFSET;
static {
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
UNSAFE = (Unsafe) f.get(null);
VALUE_OFFSET = UNSAFE.objectFieldOffset(
UnsafeAtomicInteger.class.getDeclaredField("value"));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private volatile int value;
public UnsafeAtomicInteger(int initialValue) {
this.value = initialValue;
}
public final int get() {
return value;
}
public final void set(int newValue) {
value = newValue;
}
// CAS 操作
public final boolean compareAndSet(int expect, int update) {
return UNSAFE.compareAndSwapInt(this, VALUE_OFFSET, expect, update);
}
// 原子自增,返回旧值
public final int getAndIncrement() {
return UNSAFE.getAndAddInt(this, VALUE_OFFSET, 1);
}
// lazy set:仅保证可见性的有序写入,不保证立即对其他线程可见
public final void lazySet(int newValue) {
UNSAFE.putOrderedInt(this, VALUE_OFFSET, newValue);
}
public static void main(String[] args) throws InterruptedException {
UnsafeAtomicInteger counter = new UnsafeAtomicInteger(0);
int threads = 10;
int perThread = 100_000;
Thread[] pool = new Thread[threads];
for (int i = 0; i < threads; i++) {
pool[i] = new Thread(() -> {
for (int j = 0; j < perThread; j++) {
counter.getAndIncrement();
}
});
pool[i].start();
}
for (Thread t : pool) t.join();
System.out.println("Expected: " + (threads * perThread));
System.out.println("Actual: " + counter.get());
}
}
5.2 示例:堆外内存的分配与使用
java
import sun.misc.Unsafe;
public class OffHeapMemoryDemo {
private static final Unsafe UNSAFE = UnsafeUtils.getUnsafe();
public static void main(String[] args) {
long size = 1024 * 1024; // 1MB
long address = UNSAFE.allocateMemory(size);
try {
// 批量置零
UNSAFE.setMemory(address, size, (byte) 0);
// 写入数据
for (long i = 0; i < 100; i++) {
UNSAFE.putByte(address + i, (byte) (i % 256));
}
// 读取数据
for (long i = 0; i < 10; i++) {
System.out.print(UNSAFE.getByte(address + i) + " ");
}
System.out.println();
// 内存拷贝
long dest = UNSAFE.allocateMemory(200);
UNSAFE.copyMemory(address, dest, 200);
UNSAFE.freeMemory(dest);
} finally {
// 必须手动释放,否则内存泄漏
UNSAFE.freeMemory(address);
}
}
}
5.3 示例:绕过构造器 + park/unpark
java
import sun.misc.Unsafe;
public class AllocateInstanceDemo {
static class Data {
private int value = 42; // 构造器/初始化不会执行
public Data() {
System.out.println("Constructor called!"); // 不会打印
}
}
public static void main(String[] args) throws Exception {
Unsafe unsafe = UnsafeUtils.getUnsafe();
// 绕过构造器创建对象
Data data = (Data) unsafe.allocateInstance(Data.class);
System.out.println("value = " + data.value); // 0,不是 42
// park/unpark 示例
Thread worker = new Thread(() -> {
System.out.println("Worker: parking...");
unsafe.park(false, 0L); // 无限期挂起
System.out.println("Worker: resumed!");
});
worker.start();
Thread.sleep(1000);
System.out.println("Main: unparking worker...");
unsafe.unpark(worker);
worker.join();
}
}
六、业内成熟范例:Netty 对 Unsafe 的深度使用
在众多使用 Unsafe 的开源项目中,Netty 是最具代表性的一个。作为高性能网络通信框架,Netty 对延迟和吞吐量的极致追求使其深度依赖 Unsafe 的堆外内存操作能力。
6.1 核心入口:PlatformDependent
Netty 将所有 Unsafe 相关操作封装在 io.netty.util.internal.PlatformDependent 和 PlatformDependent0 中。PlatformDependent0 是更底层的封装,直接持有 Unsafe 实例:
java
// PlatformDependent0 中的核心字段和方法
private static final Unsafe UNSAFE;
private static final long ADDRESS_SIZE;
static {
Unsafe unsafe = null;
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
unsafe = (Unsafe) f.get(null);
} catch (Throwable cause) {
unsafe = null;
}
UNSAFE = unsafe;
ADDRESS_SIZE = unsafe != null ? unsafe.addressSize() : 0;
}
static long allocateMemory(long size) {
return UNSAFE.allocateMemory(size);
}
static void freeMemory(long address) {
UNSAFE.freeMemory(address);
}
static long reallocateMemory(long address, long newSize) {
return UNSAFE.reallocateMemory(address, newSize);
}
6.2 用途一:无 Cleaner 的直接缓冲区
JDK 的 ByteBuffer.allocateDirect() 创建的直接缓冲区内部带有一个 Cleaner 对象,用于在 GC 时自动释放堆外内存。但这种方式有两个问题:
- 内存释放依赖 GC:直接缓冲区在堆上占用很小,不会触发 GC,导致堆外内存迟迟不释放。
- JDK 的兜底机制 :当直接内存分配达到上限时,JDK 会显式触发
System.gc()然后 sleep 100ms 等待 Cleaner 执行------这在 Netty 的 EventLoop 线程中是不可接受的。
Netty 的解决方案是 USE_DIRECT_BUFFER_NO_CLEANER 模式:直接用 Unsafe.allocateMemory() 分配内存,然后用 Unsafe.freeMemory() 手动释放,完全绕过 JDK 的 Cleaner 机制:
java
// 分配无 Cleaner 的直接缓冲区
static ByteBuffer allocateDirectNoCleaner(int capacity) {
assert USE_DIRECT_BUFFER_NO_CLEANER;
// 用 Unsafe 分配堆外内存
long memory = allocateMemory(capacity);
// 包装成 ByteBuffer(通过反射构造 DirectByteBuffer)
return newDirectBuffer(memory, capacity);
}
// 释放无 Cleaner 的直接缓冲区
static void freeDirectNoCleaner(ByteBuffer buffer) {
assert USE_DIRECT_BUFFER_NO_CLEANER;
int capacity = buffer.capacity();
// 获取缓冲区的内存地址并释放
freeMemory(directBufferAddress(buffer));
// 更新内存计数器
decrementMemoryCounter(capacity);
}
6.3 用途二:访问 DirectByteBuffer 的 Cleaner
当不使用无 Cleaner 模式时,Netty 仍然需要主动触发 Cleaner 来及时释放内存,而不是等待 GC。它通过 Unsafe 获取 DirectByteBuffer 内部的 cleaner 字段:
java
// CleanerJava6 中的实现
if (PlatformDependent0.hasUnsafe()) {
ByteBuffer direct = ByteBuffer.allocateDirect(1);
try {
Field cleanerField = direct.getClass().getDeclaredField("cleaner");
// 获取 cleaner 字段的偏移量
fieldOffset = PlatformDependent0.objectFieldOffset(cleanerField);
// 读取 cleaner 对象
Object cleaner = PlatformDependent0.getObject(direct, fieldOffset);
// 获取 clean 方法
clean = cleaner.getClass().getDeclaredMethod("clean");
} ...
}
// 主动清理直接缓冲区
public static void freeDirectBuffer(ByteBuffer buffer) {
if (buffer.isDirect()) {
// 通过 Unsafe 调用 Cleaner.clean()
CleanerJava6.freeDirectBuffer(buffer);
}
}
JDK 9+ 还提供了 Unsafe.invokeCleaner(ByteBuffer) 方法,更直接地触发清理:
java
// JDK 9+ 可以直接调用
UNSAFE.invokeCleaner(directBuffer);
6.4 用途三:内存计数器
Netty 维护了一个 DIRECT_MEMORY_COUNTER(AtomicLong),用于跟踪已分配的堆外内存总量,在达到上限时抛出 OutOfMemoryError:
java
private static final AtomicLong DIRECT_MEMORY_COUNTER;
private static void incrementMemoryCounter(int capacity) {
if (DIRECT_MEMORY_COUNTER != null) {
for (;;) {
long usedMemory = DIRECT_MEMORY_COUNTER.get();
long newUsedMemory = usedMemory + capacity;
if (newUsedMemory > DIRECT_MEMORY_LIMIT) {
throw new OutOfMemoryError("...");
}
// CAS 更新计数器
if (DIRECT_MEMORY_COUNTER.compareAndSet(usedMemory, newUsedMemory)) {
break;
}
}
}
}
6.5 Netty 面对 Unsafe 淘汰的应对
随着 JDK 23 弃用 Unsafe 内存方法、JDK 24 默认输出警告,Netty 也在积极迁移:
- Netty 4.1.x :仍依赖 Unsafe,用户需要加
--sun-misc-unsafe-memory-access=allow来消除警告。4.1.120/121 曾尝试默认禁用 Unsafe,但因性能回退过大和 JNI 警告无法规避而在 4.1.122 回退。 - Netty 4.2.2+ :新增基于 FFM API(
MemorySegment) 的实现,提供两种模式:- 首选模式 :通过 FFM downcall 直接链接系统
malloc/free,开销最小(Java 24+,需--enable-native-access=io.netty.common)。 - 回退模式 :使用共享
Arena分配内存,无需 native access,但释放时涉及 thread-local handshake 和 JIT 反优化,开销较大(Java 25+)。
- 首选模式 :通过 FFM downcall 直接链接系统
Netty 的迁移路径本身就是 Unsafe 生态演进的一个缩影。
七、Oracle JDK 团队对 Unsafe 的未来计划
Oracle 对 Unsafe 的态度从一开始就很明确:这是一个过渡方案,最终会被安全的标准 API 取代。 随着 VarHandle 和 FFM API 的成熟,淘汰计划已经进入执行阶段。
7.1 两大标准替代 API
在正式淘汰之前,JDK 团队先准备好了替代品:
| 替代 API | 引入版本 | 替代范围 |
|---|---|---|
java.lang.invoke.VarHandle |
JDK 9(JEP 193,2017) | On-heap 内存操作:字段读写、volatile 语义、CAS、数组元素访问 |
java.lang.foreign.MemorySegment + Arena |
JDK 22(JEP 454,2023) | Off-heap 内存操作:分配、释放、读写、拷贝、与原生代码交互 |
这两个 API 的共同特点是:
- 保证无未定义行为:不会因为非法地址导致 JVM 崩溃。
- 长期稳定:属于 Java 标准 API,不会随意变更。
- 与工具链深度集成:有完善的文档、JFR 事件支持、调试器支持。
7.2 五阶段淘汰计划
根据 JEP 471(JDK 23) 和 JEP 498(JDK 24),Unsafe 内存访问方法的淘汰分为五个阶段:
yaml
阶段 1: JDK 23 (2024年9月) ------ 编译期弃用
阶段 2: JDK 24 (2025年3月) ------ 运行时警告(默认)
阶段 3: JDK 26 (2026年9月) ------ 默认抛异常
阶段 4: JDK 26+ ------ 移除 on-heap 方法
阶段 5: JDK 26+ ------ 移除 off-heap 和 bimodal 方法
阶段 1:JDK 23 --- 编译期弃用
JDK 23 将所有 79 个内存访问方法标记为 @Deprecated(since="23", forRemoval=true)。编译时会产生弃用警告:
arduino
warning: [removal] compareAndSwapInt(Object,long,int,int) in Unsafe has been deprecated and marked for removal
同时新增命令行选项 --sun-misc-unsafe-memory-access={allow|warn|debug|deny},默认值为 allow。
阶段 2:JDK 24 --- 运行时警告
JDK 24(JEP 498)将默认值改为 warn。当应用首次调用任何 Unsafe 内存方法时,会输出警告:
arduino
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
WARNING: sun.misc.Unsafe::setMemory has been called by com.foo.bar.Server (file:/tmp/foobarserver/thing.jar)
WARNING: Please consider reporting this to the maintainers of com.foo.bar.Server
WARNING: sun.misc.Unsafe::setMemory will be removed in a future release
无论调用多少个方法、多少次,只输出一次警告 。用户可以通过 --sun-misc-unsafe-memory-access=allow 关闭警告。
阶段 3:JDK 26 --- 默认抛异常
JDK 26 计划将默认值改为 deny,调用 Unsafe 内存方法会直接抛出 UnsupportedOperationException。用户可以回退到 warn 模式,但不能再使用 allow。
阶段 4 & 5:物理移除
JDK 26 之后的版本将逐步物理删除方法:
- 先移除 on-heap 方法(因为替代品 VarHandle 从 JDK 9 就有了,迁移时间最充分)。
- 后移除 off-heap 和 bimodal 方法(替代品 FFM API 从 JDK 22 才有,需要更多迁移时间)。
- 两者也可能在合适时机同时移除。
7.3 命令行选项详解
--sun-misc-unsafe-memory-access 是整个淘汰过程的核心控制开关:
| 模式 | 行为 |
|---|---|
allow |
允许使用,无运行时警告(JDK 23 默认) |
warn |
允许使用,首次调用输出一次警告(JDK 24 默认) |
debug |
允许使用,每次调用都输出警告 + 堆栈跟踪 |
deny |
禁止使用,每次调用抛 UnsupportedOperationException(JDK 26 默认) |
7.4 非内存方法的命运
JEP 471 明确表示:本次只针对内存访问方法,不打算移除整个 Unsafe 类。 剩余的 8 个非内存方法(如 park/unpark、allocateInstance、addressSize 等)将在未来单独评估和弃用。
事实上,Unsafe 的非内存方法已经在逐步被移除:
defineClass()--- JDK 11 移除(替代:MethodHandles.Lookup.defineClass)defineAnonymousClass()--- JDK 17 移除(替代:MethodHandles.Lookup.defineHiddenClass)ensureClassInitialized()/shouldBeInitialized()--- JDK 22 移除(替代:MethodHandles.Lookup.ensureInitialized)
7.5 大背景:Integrity by Default
移除 Unsafe 内存方法并非孤立行动,而是 Oracle 推动的 "Integrity by Default"(默认完整性) 战略的一部分。该战略还包括:
- JEP 472:限制 JNI(Java Native Interface)
- JEP 451:限制动态加载 Agent
- JEP 260:封装大多数内部 API
目标是让 Java 平台更安全、更可维护、性能更可预测,同时减少应用因为依赖不稳定的内部 API 而被"锁定"在旧版 JDK 上的风险。
7.6 给开发者的建议
- 新代码不要使用 Unsafe :直接使用
VarHandle和MemorySegment。 - 现有代码逐步迁移 :利用 JDK 24 的警告和 JFR 的
jdk.DeprecatedInvocation事件定位使用点。 - 不要迁移到其他内部 API :Oracle 明确警告不要从
sun.misc.Unsafe迁移到 JDK 内部的其他 unsupported 类(如jdk.internal.misc.Unsafe)。 - 关注依赖库的升级:Netty、Hadoop、Cassandra 等主流项目都在积极迁移,及时升级依赖版本。
八、总结
sun.misc.Unsafe 是 Java 平台发展史上一个独特的存在:
- 它是一把双刃剑:赋予了 Java 直接操作内存和硬件指令的能力,支撑了整个高性能中间件生态,但也带来了崩溃、泄漏和安全风险。
- 它是一个时代的产物:2002 年诞生于标准 API 缺失的背景下,本应是 JDK 内部的临时工具,却因为生态的需求而"意外"成为公共基础设施。
- 它正在走向终点:随着 VarHandle(JDK 9)和 FFM API(JDK 22)的成熟,Oracle 已经启动了五阶段淘汰计划,从 JDK 23 的弃用到 JDK 26+ 的物理移除,路线清晰。
对于 Java 开发者而言,理解 Unsafe 的原理和使用方式仍然有价值------它能帮助开发者深入理解 JVM 内存模型、并发原理和高性能框架的内部实现。但在实际工程中,是时候告别 Unsafe,拥抱标准 API 了。