目录
一、概述
Unsafe类可以直接访问系统内存资源、自主管理内存资源,由于过于底层 ,不建议自己调用Unsafe类,容易造成JVM异常,但是JUC又经常使用这个类,所以有必要了解其原理。

Unsafe对象是单例的:
java
public final class Unsafe {
// 单例对象
private static final Unsafe theUnsafe;
......
private Unsafe() {
}
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
// 仅在引导类加载器`BootstrapClassLoader`加载时才合法
if(!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
}
只能基于反射获取Unsafe对象,通过getUnsafe()方法获取会抛异常:
java
private static Unsafe reflectGetUnsafe() {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe) field.get(null);
} catch (Exception e) {
log.error(e.getMessage(), e);
return null;
}
}
二、内存操作
Unsafe类提供了如下接口操作内存,不仅可以操作JVM堆内存,还能操作JVM外的内存。
java
//获取内存空间,返回内存空间地址
public native long allocateMemory(long bytes);
//重新调整内存空间的大小,当address后的空间足够扩容则会原地扩容,否则拷贝数据到新的内存空间并返回地址
public native long reallocateMemory(long address, long bytes);
//将内存设置为指定值
public native void setMemory(Object o, long offset, long bytes, byte value);
//内存拷贝
public native void copyMemory(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes);
//释放内存
public native void freeMemory(long address);
1.DirectByteBuffer类
DirectByteBuffer类用于申请堆外内存 ,底层就是调用Unsafe类的API。I/O过程中会将数据从JVM堆内存拷贝到堆外内存,有一定耗时,所以将频繁I/O的数据直接存储到堆外内存能够提升性能。
java
DirectByteBuffer(int cap) {
super(-1, 0, cap, cap);
boolean pa = VM.isDirectMemoryPageAligned();
int ps = Bits.pageSize();
long size = Math.max(1L, (long)cap + (pa ? ps : 0));
Bits.reserveMemory(size, cap);
long base = 0;
try {
// 分配内存并返回基地址
base = unsafe.allocateMemory(size);
} catch (OutOfMemoryError x) {
Bits.unreserveMemory(size, cap);
throw x;
}
// 内存初始化
unsafe.setMemory(base, size, (byte) 0);
if (pa && (base % ps != 0)) {
// Round up to page boundary
address = base + ps - (base & (ps - 1));
} else {
address = base;
}
// 构建Cleaner对象用于跟踪DirectByteBuffer对象的垃圾回收,以实现当DirectByteBuffer被垃圾回收时,分配的堆外内存一起被释放
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
att = null;
}
三、内存屏障
内存屏障用于确保主内存和多线程工作内存中数据的同步 ,保证数据可见性 。Java线程:volatile关键字(解决可见性)
java
//内存屏障,禁止读操作重排序。
public native void loadFence();
//内存屏障,禁止store操作重排序。
public native void storeFence();
//内存屏障,禁止读写操作重排序
public native void fullFence();
- 写内存屏障:对于写操作,首先会修改当前线程工作内存的数据,然后将其写回主内存,但这是两步操作,期间其他线程仍然能够读到主内存的旧数据。写内存屏障就是为了立即 将屏障之前 的写操作 结果刷新到主内存 ,确保两步操作期间其他线程必须等待。并结合缓存一致性协议将其他处理器(线程工作内存)的缓存行设置为无效,但是缓存一致性协议是异步的,期间其他线程仍然能操作其工作内存中的旧数据,所以应该结合读内存屏障使用。
- 读内存屏障:对于读操作,首先会从主内存读数据到工作内存,然后操作工作内存的数据,但是操作数据期间主内存的数据可能已经被修改了,此时工作内存中的数据是脏数据。读内存屏障就是确保在屏障之后 的读操作 都能看到最新的数据 。在多处理器系统中,由于缓存一致性协议不是瞬时的,一个处理器对共享数据的修改可能不会立即被其他处理器看到。其他处理器的缓存中可能还存有旧的数据,而读屏障可以强制该处理器(线程)等待缓存一致性协议将已修改的缓存失效后,才执行读屏障后的读操作,从主内存重新加载数据,从而确保读取到最新的值。
- 全内存屏障:读屏障+写屏障,保证屏障之前的写操作对屏障之后的读操作立即可见。
java
public class MyThread extends Thread{
boolean flag = false;
@Override
public void run() {
System.out.println("thread线程执行,flag="+flag);
flag = true;
reflectGetUnsafe().storeFence();
System.out.println("thread线程执行,flag="+flag);
}
public static void main(String[] args) throws InterruptedException {
MyThread thread = new MyThread();
thread.start();
System.out.println("main线程执行,flag="+thread.flag);
reflectGetUnsafe().loadFence();
System.out.println("main线程执行,flag="+thread.flag);
}
}
四、CAS操作
CAS是一条CPU原子指令 (cmpxchg指令),Unsafe提供的CAS方法底层实现即为cmpxchg,用于实现乐观锁 。 Java线程 CAS乐观锁、AtomicInteger类源码
五、数组操作
java
//返回数组中第一个元素的地址
public native int arrayBaseOffset(Class<?> arrayClass);
//返回数组中一个元素占用的大小
public native int arrayIndexScale(Class<?> arrayClass);
1.AtomicIntegerArray类
AtomicIntegerArray可以实现对Integer数组中每个元素的原子性操作,就是通过Unsafe类的 arrayBaseOffset、arrayIndexScale进行数组中元素的定位 ,然后通过CAS实现原子性操作。
六、线程调度
java
//取消阻塞线程
public native void unpark(Object thread);
//阻塞线程
public native void park(boolean isAbsolute, long time);
//获得对象锁(可重入锁)
@Deprecated
public native void monitorEnter(Object o);
//释放对象锁
@Deprecated
public native void monitorExit(Object o);
//尝试获取对象锁
@Deprecated
public native boolean tryMonitorEnter(Object o);
1.AbstractQueuedSynchronizer类
AQS的LockSupport.park()和LockSupport.unpark()方法实际是调用Unsafe类的 park、unpark方式实现的实现线程的阻塞和唤醒的:
java
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
setBlocker(t, null);
}
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}