Unsafe
类是Java中位于sun.misc
包下的一个特殊类,它提供了底层的原子性操作和直接内存访问能力,是构建高性能并发数据结构的关键工具。这类操作通常与硬件特性紧密相关,因此对性能有极高的要求。
通常Unsafe
的使用应限于JVM和Java核心库的开发者。对于一般的应用程序开发者,推荐使用java.util.concurrent
包提供的更安全的并发抽象,这些抽象内部正是借助Unsafe
来实现的。
类定义
查看代码
public final class Unsafe {
static {
Reflection.registerMethodsToFilter(Unsafe.class, Set.of("getUnsafe"));
}
private Unsafe() {}
private static final Unsafe theUnsafe = new Unsafe();
private static final jdk.internal.misc.Unsafe theInternalUnsafe = jdk.internal.misc.Unsafe.getUnsafe();
// ...
}
public final class Unsafe
:Unsafe
类被声明为final
,这意味着它不能被继承。private Unsafe()
:构造方法是私有的,防止外部通过new
关键字创建Unsafe
的实例。
单例实例
private static final Unsafe theUnsafe = new Unsafe();
private static final Unsafe theUnsafe
:theUnsafe
是Unsafe
类的一个单例实例,它是在类加载时创建的。
获取Unsafe实例的方法
@CallerSensitive
public static Unsafe getUnsafe() {
Class<?> caller = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(caller.getClassLoader()))
throw new SecurityException("Unsafe");
return theUnsafe;
}
public static Unsafe getUnsafe()
:这是一个公共静态方法,用于返回Unsafe
的单例实例。由于这个方法是公共的,它看起来似乎可以随意调用,但实际上,它内部有安全检查,只有当调用它的类是BootstrapClassLoader
加载的类时,才会成功,否则会抛出SecurityException
。
获取Unsafe
类的实例并不直接公开,因为它是一个内部API,设计上不鼓励在普通应用程序中使用。但是,可以通过反射的方式来获取Unsafe
的单例实例。
查看代码
import jdk.internal.misc.Unsafe;
import java.lang.reflect.Field;
public class UnsafeExample {
public static void main(String[] args) {
try {
// 通过反射获取Unsafe的theUnsafe字段
Field field = Unsafe.class.getDeclaredField("theUnsafe");
// 设置字段可访问
field.setAccessible(true);
// 获取单例实例
Unsafe unsafe = (Unsafe) field.get(null);
// 现在可以使用unsafe实例进行操作
// 例如: unsafe.allocateMemory(1024); // 分配内存
} catch (Exception e) {
e.printStackTrace();
}
}
}
获取
theUnsafe
字段:javaField field = Unsafe.class.getDeclaredField("theUnsafe");
这里我们使用
getDeclaredField
方法获取Unsafe
类中名为theUnsafe
的字段。设置字段可访问:
javafield.setAccessible(true);
由于
theUnsafe
字段是私有的,我们需要通过setAccessible(true)
来覆盖Java的访问控制检查。获取单例实例:
javaUnsafe unsafe = (Unsafe) field.get(null);
通过调用
field.get(null)
,我们获取了theUnsafe
字段的值,即Unsafe
的单例实例。
相关操作
Unsafe
类提供了大量可以直接操作内存和对象的方法,这些方法大致可以分为以下几类:
内存操作
这些方法允许直接访问和操作内存。
public native long allocateMemory(long bytes);
分配一块指定大小的内存空间,并返回基地址。public native long reallocateMemory(long address, long bytes);
重新分配给定地址的内存块,返回新的基地址。public native void freeMemory(long address);
释放给定地址的内存块。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 copySwapMemory(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes, long elemSize);
类似于copyMemory
,但会在复制时对每个元素进行字节序转换。
对象操作
这些方法允许直接操作对象和类。
public native Object allocateInstance(Class<?> cls) throws InstantiationException;
分配一个类的实例,但不调用任何构造方法。public native long objectFieldOffset(Field f);
获取给定字段在对象中的偏移量。public native Object staticFieldBase(Field f);
获取给定静态字段的基地址。public native int staticFieldOffset(Field f);
获取给定静态字段在类中的偏移量。public native boolean shouldBeInitialized(Class<?> c);
确定给定的类是否应该初始化。public native void ensureClassInitialized(Class<?> c);
确保给定的类已经被初始化。
原子操作
这些方法提供了原子性操作,常用于实现并发算法。
public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object x);
原子地比较并交换对象字段。public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);
原子地比较并交换整数字段。public final native boolean compareAndSwapLong(Object o, long offset, long expected, long x);
原子地比较并交换长整数字段。public native void putOrderedObject(Object o, long offset, Object x);
有序地写入对象字段,通常用于构建非阻塞数据结构。public native void putOrderedInt(Object o, long offset, int x);
有序地写入整数字段。public native void putOrderedLong(Object o, long offset, long x);
有序地写入长整数字段。
线程挂起和恢复
这些方法用于挂起和恢复线程。
public native void unpark(Object thread);
唤醒挂起的线程。public native void park(boolean isAbsolute, long time);
挂起当前线程,直到被唤醒或超时。
在底层,park
可能会调用操作系统的同步原语,如Linux的futex
或Windows的WaitOnAddress
。
数组操作
这些方法允许直接操作数组。
public native int arrayBaseOffset(Class<?> arrayClass);
获取给定数组类型的基地址偏移量。public native int arrayIndexScale(Class<?> arrayClass);
获取给定数组类型的索引缩放因子。
内存屏障
这些方法用于创建各种内存屏障,以防止指令重排。
public native void loadFence();
在此屏障之前的加载操作全部完成之后,才能执行之后的加载操作。public native void storeFence();
在此屏障之前的存储操作全部完成之后,才能执行之后的存储操作。public native void fullFence();
在此屏障之前的加载和存储操作全部完成之后,才能执行之后的加载和存储操作。