Java Unsafe类主要功能

简介

通过Java中的Unsafe类可以执行很多较底层的操作,如直接分配堆外内存等,因此使用起来具有一定的风险,Unsafe类的名称由此得来。Unsafe类通常只提供给由BootstrapClassLoader类加载器所加载的类使用,且在JDK源码中有较广泛的使用

JDK9及以后,除了原来的sun.misc.Unsafe类之外,还多出了一个jdk.internal.misc.Unsafe类,新多出来的这个Unsafe类仅供JDK內部使用,并且功能比原来的Unsafe类更加强大。原来的Unsafe类的某些方法的实现也改为了依赖新的Unsafe

使用Unsafe

一般不建议在应用代码中直接使用Unsafe,若要使用,可通过反射取得Unsafe对象,又或者把使用Unsafe的应用类通过BootstrapClassLoader来加载

反射获得Unsafe对象

Field field = Unsafe.class.getDeclaredField("theUnsafe");  
field.setAccessible(true);  
Unsafe unsafe = (Unsafe) field.get(null);

应用类通过BootstrapClassLoader加载,通过在启动JVM时添加参数

# ${path}代表应用类路径
java -Xbootclasspath/a: ${path}

内存操作

Unsafe的内存操作地址有两种寻址方式,分别为绝对地址和相对对象引用寻址,并有相应的重载方法

绝对地址寻址

// 分配内存空间4B
long address = unsafe.allocateMemory(4);
// 绝对地址,对每个字节设置对应的值
unsafe.setMemory(address, 1, (byte)0b00110011);  
unsafe.setMemory(address + 1, 1, (byte)0b11000011);  
unsafe.setMemory(address + 2, 1, (byte)0b10011001);  
unsafe.setMemory(address + 3, 1, (byte)0b11110000);  
int anInt = unsafe.getInt(address);  
System.out.println(Integer.toBinaryString(anInt));  
// 释放内存
unsafe.freeMemory(address);

输出结果

11110000100110011100001100110011

对象引用寻址

private static class Wrapper {  
    private int value;  
}
Wrapper wrapper = new Wrapper();  
System.out.println(wrapper.value);  
  
// 计算对象属性的地址偏移量
long offset = unsafe.objectFieldOffset(Wrapper.class.getDeclaredField("value"));  
// 修改属性值
unsafe.putInt(wrapper, offset, 2);  
  
System.out.println(wrapper.value);

输出结果

0
2

内存屏障

读屏障

读屏障,读屏障前的读操作不能被重排序到读屏障后,读屏障后的读操作和写操作不能被重排序到读屏障前。CPU执行读屏障指令时,会处理invalidate queue,将该失效的缓存行设置状态为invalidate。然后再执行读屏障指令后面的操作

对应LoadLoad屏障和LoadStore屏障

unsafe.loadFence();

写屏障

写屏障,写屏障前的写操作和读操作不能被重排序到写屏障后,写屏障后的写操作不能被重排序到写屏障前。CPU执行写屏障指令时,会处理store buffer,将当前存在于store buffer的数据打上记号,在这些打上记号的数据被写入cache对应的缓存行之前,写屏障后的写操作只能存放于store buffer,无法被写入cache

对应StoreStore屏障和LoadStore屏障

unsafe.storeFence();

全能屏障

全能屏障,兼具读屏障与写屏障的功能

对应LoadLoad屏障、StoreStore屏障、LoadStore屏障和StoreLoad屏障

unsafe.fullFence();

CAS

CAS全称Compare And Swap,将内存中某个位置的值与预期值比较,若相等,则替换为新的值,返回true,若不相等,则不替换,返回false。CAS由CPU提供操作的原子性

Unsafe的CAS操作有两种寻址方式,分别为绝对地址和相对对象引用寻址。使用绝对地址时简单地把第一个参数设置为null即可

long address = unsafe.allocateMemory(4);  
unsafe.setMemory(address, 1, (byte)0b00110011);  
unsafe.setMemory(address + 1, 1, (byte)0b11000011);  
unsafe.setMemory(address + 2, 1, (byte)0b10011001);  
unsafe.setMemory(address + 3, 1, (byte)0b11110000);  
int anInt = unsafe.getInt(address);  
System.out.println(Integer.toBinaryString(anInt));  
unsafe.compareAndSwapInt(null, address, 0b11110000100110011100001100110011, 0b0);  
int afterCAS = unsafe.getInt(address);  
System.out.println(Integer.toBinaryString(afterCAS));  
unsafe.freeMemory(address);

输出结果

11110000100110011100001100110011
0

JDK 11下的compareAndSwapInt方法源码

/**  
 * Atomically updates Java variable to {@code x} if it is currently
 * holding {@code expected}
 *
 * <p>This operation has memory semantics of a {@code volatile} read
 * and write.  Corresponds to C11 atomic_compare_exchange_strong
 *
 * @return {@code true} if successful
 */
@ForceInline  
public final boolean compareAndSwapInt(Object o, long offset,  
                                       int expected,  
                                       int x) {  
    return theInternalUnsafe.compareAndSetInt(o, offset, expected, x);  
}

@HotSpotIntrinsicCandidate  
public final native boolean compareAndSetInt(Object o, long offset,  
                                             int expected,  
                                             int x);

Unsafe类提供的CAS操作,读写都自带volatile语义,可保证相关变量的可见性

线程调度

阻塞当前线程,直到被调用相应的unpark,或被中断,或超时

unsafe.park(false, 0);

唤醒线程

unsafe.unpark(thread);