一、介绍
在Java中,sun.misc.Unsafe可以认为是用于JDK内部使用的工具类,它将一些需要使用native语言实现的功能通过java方法暴露出来,这些方法比较“危险”,因为它们可以直接修改内存中的值。
通常情况下,我们并不能直接在程序中使用Unsafe,Unsafe的构造方法被私有化,语法层面上只能通过其提供的公共静态方法getUnsafe获取Unsafe实例:theUnsafe,theUnsafe在静态代码块被初始化,所以其是一个单例,如下所示:
public final class Unsafe {
//静态的、私有的实例字段
private static final Unsafe theUnsafe;
......
//私有化构造器
private Unsafe() {
}
......
//对外提供静态方法获取实例
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
//检查是否允许访问Unsafe
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
......
static {
......
//初始化Unsafe实例
theUnsafe = new Unsafe();
......
}
}
从Unsafe的代码中可以看出,通过getUnsafe方法获取Unsafe单例是唯一的“正规”途径,而该方法中对调用者classLoader做了检查,我们看一下检查代码:VM.isSystemDomainLoader:
public static boolean isSystemDomainLoader(ClassLoader var0) {
return var0 == null;
}
逻辑比较简单,主要是看classLoader是否为null,也就是判断该类是不是由BootStrap加载,如果不是的话,则不具有访问Unsafe的权限,随即抛出SecurityException异常。
import sun.misc.Unsafe;
public class Test{
public static void main(String[] args){
Unsafe unsafe = Unsafe.getUnsafe();
}
}
上述代码尝试获取一个Unsafe实例,会抛出异常异常:
Exception in thread "main" java.lang.SecurityException: Unsafe
at sun.misc.Unsafe.getUnsafe(Unsafe.java:90)
二、使用Unsafe
虽然我们没有权限调用Unsafe#getUnsafe方法,但是我们还是有办法能够获取到Unsafe实例。既然theUnsafe字段在静态代码块中被初始化了,并且它是一个静态变量,那么我们可以直接通过反射获取该字段值。
在下面的代码中,我们先获取到了Unsafe实例,并且通过Unsafe#putInt方法将Test实例的value字段设置为100,然后通过Unsafe#getIntVolatile方法获取该值,并输出:
public class Test {
private int value;
public static void main(String[] args) throws Exception {
//获取theUnsafe字段
Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
//私有变量设置访问权限
unsafeField.setAccessible(true);
//theUnsafe是静态变量,直接通过Field#get(null)获取
Unsafe theUnsafe = (Unsafe) unsafeField.get(null);
//创建Test实例
Test test = new Test();
//获取value字段在Test中的偏移量
long fieldOffset = theUnsafe.objectFieldOffset(Test.class.getDeclaredField("value"));
//直接操作该内存,设置值
theUnsafe.putInt(test, fieldOffset, 100);
//获取该偏移的值
System.out.println(theUnsafe.getIntVolatile(test, fieldOffset));
}
}
控制台输出为:100
三、源码实现
Unsafe中的所有基础方法都属于native方法,这里我们就不一一解析了,就看一下JUC中经常使用的CAS源码实现即可,至于其它方法,读者朋友可以根据自身情况酌情研究。
在openjdk的源码中,Unsafe主要的实现代码在unsafe.cpp中,其中包含了native方法操作内存的实现细节。这里我们主要看看以下两个native方法的实现:
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);
2.1 compareAndSwapInt
我们首先看看compareAndSwapInt方法在openjdk中的实现:
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
UnsafeWrapper("Unsafe_CompareAndSwapInt");
oop p = JNIHandles::resolve(obj);
jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END
首先是JNIHandles::resolve(obj)方法。该方法定义在jniHandles.hpp中:
inline oop JNIHandles::resolve(jobject handle) {
oop result = (handle == NULL ? (oop)NULL : *(oop*)handle);
assert(result != NULL || (handle == NULL || !CheckJNICalls || is_weak_global_handle(handle)), "Invalid value read from jni handle");
assert(result != badJNIHandle, "Pointing to zapped jni handle area");
return result;
};
可以看到,该方法定义为一个内联函数,没有做什么复杂的事儿,就是把jobject参数转成了oop。
然后调用index_oop_from_field_offset_long(p, offset);方法,其中offset为修改的字段在对象所占内存中的偏移位置(相关信息可参考Java对象内存布局),最终得到的addr就是该字段在内存中的位置,这里可以简单理解为对象地址加上offset。
得到字段地址addr之后就会调用核心的Atomic::cmpxchg方法,该方法定义在atomic.hpp中,除了cmpxchg,还有xchg、cmpxchg_ptr等等,我们简单看一下:
class Atomic : AllStatic {
public:
// Atomic operations on jlong types are not available on all 32-b