简介
Unsafe类是在sun.misc包下,不属于Java标准。但是很多Java的基础类库,包括一些被广泛使用的高性能开发库都是基于Unsafe类开发的,比如Netty、Cassandra、Hadoop、Kafka等。Unsafe类在提升Java运行效率,增强Java语言底层操作能力方面起了很大的作用。
Java和C++语言的一个重要区别就是Java中我们无法直接操作一块内存区域,不能像C++中那样可以自己申请内存和释放内存。Java中的Unsafe类为我们提供了类似C++手动管理内存的能力,同时也有了指针的问题。
首先,Unsafe类是"final"的,不允许继承。且构造函数是private的:
public final class Unsafe {
private static final Unsafe theUnsafe;
public static final int INVALID_FIELD_OFFSET = -1;
private static native void registerNatives();
// 构造函数是private的,不允许外部实例化
private Unsafe() {
}
...
}
因此我们无法在外部对Unsafe进行实例化。
获取Unsafe
Unsafe无法实例化,那么怎么获取Unsafe呢?答案就是通过反射来获取Unsafe:
public Unsafe getUnsafe() throws IllegalAccessException {
Field unsafeField = Unsafe.class.getDeclaredFields()[0];
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
return unsafe;
}
主要功能
Unsafe的功能如下图:
CAS相关
JUC中大量运用了CAS操作,可以说CAS操作是JUC的基础,因此CAS操作是非常重要的。Unsafe中提供了int,long和Object的CAS操作:
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
偏移量相关
public native long staticFieldOffset(Field var1);
public native long objectFieldOffset(Field var1);
- staticFieldOffset方法用于获取静态属性Field在对象中的偏移量,读写静态属性时必须获取其偏移量。
- objectFieldOffset方法用于获取非静态属性Field在对象实例中的偏移量,读写对象的非静态属性时会用到这个偏移量
类加载
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);
public native Object allocateInstance(Class<?> var1) throws InstantiationException;
public native boolean shouldBeInitialized(Class<?> var1);
public native void ensureClassInitialized(Class<?> var1);
- defineClass方法定义一个类,用于动态地创建类。
- defineAnonymousClass用于动态的创建一个匿名内部类。
allocateInstance
方法用于创建一个类的实例,但是不会调用这个实例的构造方法,如果这个类还未被初始化,则初始化这个类。- shouldBeInitialized方法用于判断是否需要初始化一个类。
- ensureClassInitialized方法用于保证已经初始化过一个类。
举例
public class UnsafeFooTest {
private static Unsafe geUnsafe() {
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
return (Unsafe) f.get(null);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
static class Simple {
private long l = 0;
public Simple() {
this.l = 1;
System.out.println("我被初始化了");
}
public long getL() {
return l;
}
}
public static void main(String[] args) throws Exception {
// Simple simple1 = new Simple();
// System.out.println(simple1.getL());
// Simple simple2 = Simple.class.newInstance();
Unsafe unsafe = geUnsafe();
//可以绕过类的初始化,不使用
Simple s = (Simple) unsafe.allocateInstance(Simple.class);
System.out.println(s.getL());
}
}
结果:
0
- 可以发现,利用Unsafe获取实例,不会调用构造方法
普通读写
通过Unsafe可以读写一个类的属性,即使这个属性是私有的,也可以对这个属性进行读写。
读写一个Object属性的相关方法
public native int getInt(Object var1, long var2);
public native void putInt(Object var1, long var2, int var4);
- getInt用于从对象的指定偏移地址处读取一个int。
- putInt用于在对象指定偏移地址处写入一个int。其他的primitive type也有对应的方法。
举例
public class UnsafeFooTest {
private static Unsafe geUnsafe() {
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
return (Unsafe) f.get(null);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
static class Guard{
private int ACCESS_ALLOWED = 1;
private boolean allow(){
return 50 == ACCESS_ALLOWED;
}
public void work(){
if (allow()){
System.out.println("我被允许工作....");
}
}
}
public static void main(String[] args) throws Exception {
Unsafe unsafe = geUnsafe();
Guard guard = new Guard();
// guard.work();
Field f = guard.getClass().getDeclaredField("ACCESS_ALLOWED");
//直接给那块内存赋值
unsafe.putInt(guard,unsafe.objectFieldOffset(f),50);
System.out.println("强行赋值...");
guard.work();
}
}
结果
强行赋值…
我被允许工作…
类加载
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);
public native Object allocateInstance(Class<?> var1) throws InstantiationException;
public native boolean shouldBeInitialized(Class<?> var1);
public native void ensureClassInitialized(Class<?> var1);
- defineClass方法定义一个类,用于动态地创建类。
- defineAnonymousClass用于动态的创建一个匿名内部类。
- allocateInstance方法用于创建一个类的实例,但是不会调用这个实例的构造方法,如果这个类还未被初始化,则初始化这个类。
- shouldBeInitialized方法用于判断是否需要初始化一个类。
- ensureClassInitialized方法用于保证已经初始化过一个类。
内存屏障
public native void loadFence();
public native void storeFence();
public native void fullFence();
- loadFence:保证在这个屏障之前的所有读操作都已经完成。
- storeFence:保证在这个屏障之前的所有写操作都已经完成。
- fullFence:保证在这个屏障之前的所有读写操作都已经完成。
线程调度
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);
- park方法和unpark方法相信看过LockSupport类的都不会陌生,这两个方法主要用来挂起和唤醒线程。
- LockSupport中的park和unpark方法正是通过Unsafe来实现的:
// 挂起线程
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker); // 通过Unsafe的putObject方法设置阻塞阻塞当前线程的blocker
UNSAFE.park(false, 0L); // 通过Unsafe的park方法来阻塞当前线程,注意此方法将当前线程阻塞后,当前线程就不会继续往下走了,直到其他线程unpark此线程
setBlocker(t, null); // 清除blocker
}
// 唤醒线程
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
monitorEnter方法和monitorExit方法用于加锁,Java中的synchronized锁就是通过这两个指令来实现的。