JavaDoc说, Unsafe提供了一组用于执行底层的,不安全操作的方法。那么具体有哪些方法呢,我画了一张图。
Unsafe 方法分类
可以看到Unsafe中提供了CAS,内存操作,线程调度,本机信息,Class相关方法,查看和设置某个对象或字段,内存分配和释放相关操作,内存地址获取相关方法。我自己抽空对上述方法进行了注释,
你可以在这里看到。
那么如何使用Unsafe呢?下面我们就来说说如何获取Unsafe并操作。
1.获取Unsafe实例
如下所述,由于Unsafe.getUnsafe会判断调用类的类加载器是否为引导类加载器,如果是,可以正常获取Unsafe实例,否则会抛出安全异常。
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
主要有两种方式来绕过安全检查,一种是通过将使用Unsafe的类交给bootstrap class loader去加载,另一种方式是通过反射。
1.1 通过bootstrap class loader去加载Unsafe。
public class GetUnsafeFromMethod {
public static void main(String[] args){
//调用这个方法,必须要在启动类加载器中获取,否则会抛出安全异常
Unsafe unsafe = Unsafe.getUnsafe();
System.out.printf("addressSize=%d, pageSize=%d\n", unsafe.addressSize(), unsafe.pageSize());
}
}
上面代码,直接执行会报安全异常SecurityException,原因是当前caller的类加载器是应用类加载器(Application Class loader),而要求的是启动类加载器,
因而!VM.isSystemDomainLoader(var0.getClassLoader()) 返回false,抛出异常。
但是通过下面的命令行,我们把GetUnsafeFromMethod.java 追加到bootclasspath(启动类加载路径)上,就可以正常执行了。
javac -source 1.8 -encoding UTF8 -bootclasspath "%JAVA_HOME%/jre/lib/rt.jar;." GetUnsafeFromMethod.java
java -Xbootclasspath:"%JAVA_HOME%/jre/lib/rt.jar;." GetUnsafeFromMethod
你也看到了这样做有点费事了,难不成每次启动都要加这么一大串指令,所以下面我们就来反射是不是好用些。
1.2 通过反射获取Unsafe
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class GetUnsafeFromReflect {
public static void main(String[] args){
Unsafe unsafe = getUnsafe();
System.out.printf("addressSize=%d, pageSize=%d\n", unsafe.addressSize(), unsafe.pageSize());
}
public static Unsafe getUnsafe() {
Unsafe unsafe = null;
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
unsafe = (Unsafe) f.get(null);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return unsafe;
}
}
嗯,通过反射就可以直接用了,是不是比上一种利用启动类加载器加载的方式好用很多
3.Unsafe API 的使用
具体如何使用,可以查看这篇文章。
实际的应用案例,可以查看美团的一篇文章。
4. Unsafe 中CAS部分的实现
我们可以看到Unsafe中基本都是调用native方法,如果你比较好奇这个native方法又是如何实现的,那么就需要去JVM里面找对应的实现。
到http://hg.openjdk.java.net/ 进行一步步选择下载对应的hotspot版本,我这里下载的是http://hg.openjdk.java.net/jdk8u/jdk8u60/hotspot/archive/tip.tar.gz,
然后解hotspot目录,发现 \src\share\vm\prims\unsafe.cpp,这个就是对应jvm相关的c++实现类了。
比如我们对CAS部分的实现很感兴趣,就可以在该文件中搜索compareAndSwapInt,此时可以看到对应的JNI方法为Unsafe_CompareAndSwapInt
// These are the methods prior to the JSR 166 changes in 1.6.0
static JNINativeMethod methods_15[] = {
...
{CC"compareAndSwapObject", CC"("OBJ"J"OBJ""OBJ")Z", FN_PTR(Unsafe_CompareAndSwapObject)},
{CC"compareAndSwapInt", CC"("OBJ"J""I""I"")Z", FN_PTR(Unsafe_CompareAndSwapInt)},
{CC"compareAndSwapLong", CC"("OBJ"J""J""J"")Z", FN_PTR(Unsafe_CompareAndSwapLong)}
...
};
接着我们在搜索Unsafe_CompareAndSwapInt的实现,
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; //执行Atomic类中的cmpxchg。
UNSAFE_END
可以看到最后会调用到Atomic::cmpxchg里面的函数,这个根据不同操作系统和不同CPU会有不同的实现,但都放在hotspot\src\os_cpu 目录下,比如linux_64x的,对应类就是hotspot\src\os_cpu\linux_x86\vm\atomic_linux_x86.inline.hpp, 而windows_64x的,对应类就是hotspot\src\os_cpu\windows_x86\vm\atomic_windows_x86.inline.hpp。(此处也说明了为什么Java可以Write once, Run everywhere, 原因就是JVM源码对不同操作系统和不同CPU有不同的实现)
这里我们以linux_64x的为例,查看Atomic::cmpxchg的实现
inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {
int mp = os::is_MP();
__asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"
: "=a" (exchange_value)
: "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
: "cc", "memory");
return exchange_value;
}
0.os::is_MP
os::is_MP()在hotspot\src\share\vm\runtime\os.hpp中,如下:
// Interface for detecting multiprocessor system
static inline bool is_MP() {
// During bootstrap if _processor_count is not yet initialized
// we claim to be MP as that is safest. If any platform has a
// stub generator that might be triggered in this phase and for
// which being declared MP when in fact not, is a problem - then
// the bootstrap routine for the stub generator needs to check
// the processor count directly and leave the bootstrap routine
// in place until called after initialization has ocurred.
return (_processor_count != 1) || AssumeMP;
}
1.__asm__: 表示接下来是内联的汇编代码,这里使用asm语句可以将汇编指令直接包含在C代码中,主要是为了极致的性能。
2.volatile: 表示去掉优化
3.LOCK_IF_MP 是一个宏定义,即:#define LOCK_IF_MP(mp) "cmp $0, " #mp "; je 1f; lock; 1: "
替换文