Unsafe 源码分析

摘要:

  1. 如何获取 Unsafe 实例
  2. 如何利用 Unsafe API 绕开 JVM的控制
  3. CAS 到底是什么
  4. 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. 功能介绍

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;