Unsafe 源码分析

TOP 带着问题看源码

  1. 如何获取 Unsafe 实例
  2. 如何利用 Unsafe API 绕开 JVM的控制
  3. CAS 到底是什么
  4. Unsafe 中的线程调度是怎么回事

1. 基本介绍

Unsafe 是用于在实质上扩展 Java 语言表达能力、便于在 Java 代码里实现原本要在 C 层实现的核心库功能用的。这些功能包括裸内存的申请、释放、访问,低层硬件的 atomic/volatile 支持,创建未初始化对象等。但由于 Unsafe 类使 Java 语言拥有不应该暴露的骚操作,增加了程序出问题的风险。

1.1 获取 Unsafe 实例

1
2
3
4
5
6
7
8
9
10
11
public class UnsafeTest {
private static Unsafe unsafe;
public static void main(String[] args) throws Exception {
Class c = UnsafeTest.class.getClassLoader().loadClass("sun.misc.Unsafe");
Field f = c.getDeclaredField("theUnsafe");
f.setAccessible(true);
unsafe = (Unsafe)f.get(c);

unsafe.xx();
}
}

回到 TOP 1 可以明白,通过反射获取 unsafe 实例。

2. 功能介绍

img

3. 数组相关

1
2
3
4
// 返回数组中第一个元素的偏移地址
public native int arrayBaseOffset(Class<?> var1);
// 返回数组中一个元素占用的大小
public native int arrayIndexScale(Class<?> var1);

通过定位数组第一个元素的偏移地址和每个元素占用的大小。

例如第一个元素偏移地址是16,存的是 int 类型,则可以通过要查询的 index * 4 + 16 来获取到对应的值。

我们可以在 AtomicIntegerArray 中看到这些操作,不过作者巧妙的通过位运算来计算index对应的偏移量。

1
2
3
4
5
6
7
8
9
10
// 1. first index 偏移量
private static final int base = unsafe.arrayBaseOffset(int[].class);
// 2. scale = 4;
int scale = unsafe.arrayIndexScale(int[].class);
// 3. 计算 scale 二进制后面有几个0,如scale = 4(0100),shift = 2
shift = 31 - Integer.numberOfLeadingZeros(scale);
// 4. 根据index对scale进行乘法运算获取偏移量 offset,如index = 1,offset = 4(1 << 2) + 16 = 20
offset = index << shift + base;
// 5. 通过 offset 原子的获取对应的值
unsafe.getIntVolatile(array, offset);

4. 内存屏障

1
2
3
4
5
6
// 内存屏障,禁止 load 操作重排序
public native void loadFence();
// 内存屏障,禁止 store 操作重排序
public native void storeFence();
// 内存屏障,禁止 load、store 操作重排序
public native void fullFence();

内存屏障主要是避免 CPU 或者 编译器对代码重排序。

如并发包中 StampedLock 解决因代码重排序校验不准确,采用loadFence()。

1
2
3
4
public boolean validate(long stamp) {
U.loadFence();
return (stamp & SBITS) == (state & SBITS);
}

5. 系统相关

1
2
3
4
// 获取系统指针的大小, 64 位是8
public native int addressSize();
// 获取内存页大小,2的幂次方,我本机测试是4096
public native int pageSize();

可以根据内存页大小计算分配页数

6. 线程调度

1
2
3
4
5
6
7
8
9
10
// 取消阻塞
public native void unpark(Object var1);
// 阻塞直到超时或中断等条件
public native void park(boolean var1, long var2);
// 弃用,获取对象锁
public native void monitorEnter(Object var1);
// 弃用,释放对象锁
public native void monitorExit(Object var1);
// 弃用,尝试获取对象锁
public native boolean tryMonitorEnter(Object var1);

大名鼎鼎的 AQS 就是通过 park、unpark 来对线程阻塞和唤醒的

回到 TOP 4 可以明白其实就是 park unpark

7. 内存操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 内存分配,相当于c++的os::malloc
public native long allocateMemory(long var1);
// 扩容内存
public native long reallocateMemory(long var1, long var3);
// 给定的内存块中设置值
public native void setMemory(Object var1, long var2, long var4, byte var6);
// 内存拷贝
public native void copyMemory(Object var1, long var2, Object var4, long var5, long var7);
// 释放内存,相当于c++的os::free
public native void freeMemory(long var1);
// 获取给定地址的XX类型的值
public native byte getXx(long var1);
// 为给定地址设置XX类型的值
public native void putXx(long var1, xx var3);

以上的内存操作针对的都是堆外内存操作,与我们平时自己创建的对象都在堆内不同,堆外不会受到 JVM 内存管理,合理使用可以减少原本堆内内存使 GC 时间减少。

java.nio.DirectByteBuffer 中利用了堆外内存减少堆内堆外的copy

回到 TOP 2 可以明白使用堆外内存操作可以绕开 JVM 控制

8. CAS(Compare And Swap, 比较和替换)

1
2
// 根据第二个参数”偏移量”去拿偏移量这么多的属性的值和第三个参数对比,如果相同则将该属性值替换为第四个参数。该偏移量是指某个字段相对Java对象的起始位置的偏移量,可以通过unsafe.objectFieldOffset(param)去获取对应属性的偏移量。
public final native boolean compareAndSwapXx(Object var1, long var2, Xx var4, Xx var5);

CAS 是一条 CPU 的原子指令(cmpxchg),如果是多核处理器会加上 LOCK 前缀

1
2
3
4
5
6
7
8
inline jint     Atomic::cmpxchg    (jint     exchange_value, volatile jint*     dest, jint     compare_value) {
int mp = os::is_MP();
__asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"
: "=a" (exchange_value)
: "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
: "cc", "memory");
return exchange_value;
}

CAS 在并发包中被广泛应用,回到 TOP 3 可以明白 CAS 是一条 CPU 的原子指令。

9. Class 相关

1
2
3
4
5
6
7
8
9
10
11
12
// 获取静态字段的内存地址偏移量
public native long staticFieldOffset(Field var1);
// 获取一个静态类中给定字段的对象指针
public native Object staticFieldBase(Field var1);
// 判断是否需要初始化一个类,因为有可能类还没初始化却去获取静态属性
public native boolean shouldBeInitialized(Class<?> var1);
// 检测类是否已经初始化
public native void ensureClassInitialized(Class<?> var1);
// 定义一个类
public native Class<?> defineClass(String var1, byte[] var2, int var3, int var4, ClassLoader var5, ProtectionDomain var6);
// 定义一个匿名类
public native Class<?> defineAnonymousClass(Class<?> var1, byte[] var2, Object[] var3);

10. 对象操作

1
2
3
4
5
6
7
8
9
10
// 返回对象某个属性相对对象内存地址的偏移量
public native long objectFieldOffset(Field var1);
// 从对象的指定偏移量处获取变量的引用,使用volatile的加载语义
public native Object getObjectVolatile(Object o, long offset);
// 存储变量的引用到对象的指定的偏移量处,使用volatile的存储语义
public native void putObjectVolatile(Object o, long offset, Object x);
// 有序、延迟版本的putObjectVolatile方法,不保证值的改变被其他线程立即看到。只有在field被volatile修饰符修饰时有效
public native void putOrderedObject(Object o, long offset, Object x);
// 绕过构造方法、初始化代码来创建对象
public native Object allocateInstance(Class<?> cls) throws InstantiationException;