Unsafe类
Unsafe类是什么
sun.misc.Unsafe 是 Java 中一个非常特殊的类。正如其名“不安全 (Unsafe)”,它提供了一组可以直接操作内存、操作线程、操作 CPU 的底层能力。
在正常的 Java 开发中,由于有 JVM 的保护,程序员是不允许直接接触这些底层细节的(比如指针操作)。但 Unsafe 类相当于给 Java 开了一个“后门”,让 Java 拥有了类似 C/C++ 指针一样强大的能力。
它是 Java 并发包(java.util.concurrent)、NIO、反射等核心功能的基石。
核心作用(四大支柱)
1. 直接操作内存(Off-Heap Memory)
功能:可以在 Java 堆外(Direct Memory)分配内存、释放内存、读写内存地址的值。
方法:allocateMemory, freeMemory, putInt, getInt 等。
应用:
NIO (DirectByteBuffer):通过 Unsafe 分配堆外内存,避免数据在 Java 堆和 Native 堆之间来回拷贝,实现零拷贝(Zero-Copy)的高性能 I/O。
2. CAS 硬件级原子操作
功能:直接封装了 CPU 的原子指令(如 cmpxchg),实现无锁并发。
方法:compareAndSwapInt, compareAndSwapObject, compareAndSwapLong。
应用:
Atomic 类:AtomicInteger, AtomicReference 等底层全靠它。
Lock 锁:AQS(AbstractQueuedSynchronizer)的状态更新也是靠它。
3. 线程调度(挂起与唤醒)
功能:直接控制线程的阻塞(Park)和唤醒(Unpark)。
方法:park, unpark。
应用:
LockSupport:LockSupport.park() 底层就是调用的 Unsafe.park()。
synchronized (重量级锁):底层也是这套机制。
4. 对象操作与内存屏障
功能:
非常规实例化:allocateInstance 可以绕过构造函数直接创建对象(序列化反序列化时常用)。
内存屏障 (Memory Fence):loadFence, storeFence 等,用于禁止指令重排序,保证 volatile 语义。
修改私有字段:通过内存偏移量(Offset)直接修改对象的 private 字段。
为什么叫 Unsafe?
因为它极其危险:
内存泄漏:手动分配的堆外内存如果不手动释放(free),GC 根本管不到,会导致严重的内存泄漏。
JVM 崩溃:如果你算错了内存地址指针,访问了非法地址,整个 JVM 进程会直接 Segfault 崩溃。
不可移植:不同 CPU 架构下的表现可能不同。
总结
Unsafe 是 Java 高性能库的“核武器”。
普通开发者平时写业务代码千万别碰它,但如果你在读 JDK 源码(尤其是并发包 JUC),你会发现到处都是它的身影。没有它,Java 就无法实现高效的并发和 I/O。
objectFieldOffset 方法
objectFieldOffset 是 sun.misc.Unsafe 类中的一个本地方法(Native Method)。
它的作用是:获取某个字段(Field)在所属对象(Object)内存布局中的相对偏移量(Offset)。
1. 通俗理解
你可以把一个 Java 对象想象成一个长方形的盒子(内存块)。
盒子的最开始放的是对象头(Header)。
接下来放的是各种成员变量(字段),比如 int a, long b, String c 等。
但是,这些字段在盒子里具体排在第几个字节的位置呢?这就需要 objectFieldOffset 来测量。
入参:一个 Field 对象(比如 AtomicInteger 类里的 value 字段)。
返回值:一个 long 类型的数字(比如 12)。
这就意味着:如果你想找 value 这个变量,请从对象的起始地址开始,向后走 12 个字节,那块地就是它的。
2. 为什么需要它?
因为 Unsafe 类提供的 CAS 方法(如 compareAndSwapInt)是直接操作内存地址的。
它不接受“字段名”作为参数(它不认识 "value" 这个字符串),它只接受“内存偏移量”。
所以,在使用 CAS 之前,必须先用 objectFieldOffset 算出来这个字段的“坐标”,存下来(通常存为静态常量 valueOffset),以后每次 CAS 操作都用这个坐标去指路。
3. 示例流程
定义:AtomicInteger 类里有一个 private volatile int value;。
测量:静态代码块调用 unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"))。
结果:假设返回 12。
使用:当你要把 value 从 0 改成 1 时:
调用 unsafe.compareAndSwapInt(obj, 12, 0, 1)。
Unsafe 就在内存中找到 obj地址 + 12 的位置,进行原子修改。
总结
objectFieldOffset 就是用来拿“藏宝图坐标”的。 拿到了坐标,Unsafe 才能挖到宝(修改数据)。
原子整数自增原理
这张图展示了 JDK 8 中 AtomicInteger 实现原子自增操作的核心源码,分为上、中、下三部分,层层递进地揭示了 CAS(Compare-And-Swap)机制的落地实现。
我们从上往下详细解析:
第一部分:上层调用 (AtomicInteger)// JDK 8 AtomicInteger 自增方法public final int incrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1;}
功能:这是我们平时最常用的 atomicInt.incrementAndGet() 方法,作用是将当前值加 1,并返回加完之后的新值。
逻辑:
它调用了 unsafe 对象的 getAndAddInt 方法。
参数解析:
this:当前 AtomicInteger 对象(你要改谁)。
valueOffset:value 字段的内存偏移量(你要改哪个字段)。
1:你要加多少(增量)。
- 注意细节:getAndAddInt 返回的是加操作之前的旧值。所以为了满足 incrementAndGet(返回新值)的语义,最后必须手动 + 1。
第二部分:核心实现 (Unsafe.class - 反编译视角)
这是 Unsafe.class 文件反编译后的代码,变量名(var1, var2...)是反编译器自动生成的,比较难读,但逻辑是真实的。
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
// var1: 对象(obj), var2: 偏移量(offset)*
// 这一步是获取主内存中当前最新的值,赋值给 var5 (作为预期值 A)*
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); *// CAS 操作*
return var5; *// 返回旧值*
}CAS 循环(自旋锁):
这是一个 do-while 循环。
核心逻辑:
先查一下现在内存里是多少(var5)。
尝试把内存里的值从 var5 改成 var5 + var4。
如果 compareAndSwapInt 返回 false(说明刚才查完之后被别人插队改了),那就不要停,继续下一轮循环。
如果返回 true(修改成功),退出循环。
第三部分:源码视角 (OpenJDK 8 Unsafe.java)
这是 OpenJDK 源码中的写法,变量名更具可读性,逻辑与上面完全一致。
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
// 1. 获取当前内存中的最新值 (Expected Value)
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, v + delta)); // 2. CAS 尝试更新
return v; // 返回的是修改前的旧值
}o:目标对象。
offset:目标字段的内存地址偏移量。
delta:增量(比如 +1)。
v:从内存中读出来的旧值。
compareAndSwapInt:
参数1:对象 o
参数2:偏移量 offset
参数3:期望值 v (只有内存当前值真的等于 v,才允许改)
参数4:新值 v + delta
总结图中的知识点
乐观锁实现:这就是典型的乐观锁(无锁)编程模式。不加 synchronized 锁,而是假设没有冲突去尝试。如果失败了(CAS 返回 false),就重试(While 循环),直到成功为止。
volatile 的作用:getIntVolatile 保证了每次读取的 v 都是主内存中最新的,防止读到 CPU 缓存里的脏数据,这是 CAS 正确性的前提。
Unsafe 的地位:AtomicInteger 只是个皮,真正的脏活累活(原子性保证、内存操作)全都是委托给 Unsafe 类去做的。