在看java原子类时里有很多方法都调用了Unsafe类方法,Unsafe类方法在jdk里没找到源码,然后下载open jdk找到了源码,在/src/share/classes/sun/misc 目录下。定义如下:
public final class Unsafe {
private static native void registerNatives();
static {
registerNatives();
sun.reflect.Reflection.registerMethodsToFilter(Unsafe.class, "getUnsafe");
}
//私有构造方法
private Unsafe() {}
//实例化Unsafe
private static final Unsafe theUnsafe = new Unsafe();
//获取Unsafe实例,这个方法会检查类加载器类型,如果非Bootstrap classloader就会抛异常
public static Unsafe getUnsafe() {
Class<?> caller = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(caller.getClassLoader()))
throw new SecurityException("Unsafe");
return theUnsafe;
}
.......
.......
.......
}
这个类不能使用Unsafe.getUnsafe()获取Unsafe实例,在getUnsafe()方法中会调用VM.isSystemDomainLoader检查类加载器,java自带三种类加载器,bootstrap类加载器是JVM启动的时候负责加载jre/lib/rt.jar 这个类是c++写的,在java中看不到。其它两个是ExtClassLoader 和 AppClassLoader都是继承ClassLoader类。isSystemDomainLoader会进行判断如果传入的null返回true,否则返回false,在启动阶段,加载rt.jar所有类的是bootstrap类加载器,所以调用caller.getClassLoader()会返回null,isSystemDomainLoader就会返回true。但是在我们自己写的类代码中直接调用这个类就不行了,此时是AppClassLoader,会返回false,直接抛异常。
/**
* Returns true if the given class loader is in the system domain
* in which all permissions are granted.
*/
public static boolean isSystemDomainLoader(ClassLoader loader) {
return loader == null;
}
所以一般用反射获取Unsafe类实例
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe)f.get(null);
这样就可以调用Unsafe类的方法。 Unsafe类很多方法都给了注释,从字面意思可以知道是干啥的,大部分方法都是native方法,所以想要弄清楚底层原理,必须看jvm的底层源码。Unsafe类里native方法实现在hotspot的src/share/vm/prims/unsafe.cpp里。先从public native int getInt(Object o, long offset)看,这个方法是从java堆对象或者数组中获取偏移offset的值。这中操作在c或者c++语言中很正常,直接通过指针就获取到了。在java中由于没有指针,所以需要通过native方法获取。这个方法对应的c++函数宏定义比较复杂,需要一步步把它还原出来。
//本地方法结构体
typedef struct {
char *name; //方法名
char *signature; //方法签名
void *fnPtr; //函数地址
} JNINativeMethod;
#define CC (char*)
#define CAST_FROM_FN_PTR(new_type, func_ptr) ((new_type)((address_word)(func_ptr)))
#define FN_PTR(f) CAST_FROM_FN_PTR(void*, &f)
#define LANG "Ljava/lang/"
#define OBJ LANG"Object;" //"Ljava/lang/""Object;"
// jdk1.8的本地方法结构体数组
static JNINativeMethod methods_18[] = {
//方法名 //方法签名 //函数入口地址
//(char*)"getObject" ((void*)((uintptr_t)(&Unsafe_GetObject)))
{CC"getObject", CC"("OBJ"J)"OBJ"", FN_PTR(Unsafe_GetObject)},
{CC"putObject", CC"("OBJ"J"OBJ")V", FN_PTR(Unsafe_SetObject)},
{CC"getObjectVolatile",CC"("OBJ"J)"OBJ"", FN_PTR(Unsafe_GetObjectVolatile)},
{CC"putObjectVolatile",CC"("OBJ"J"OBJ")V", FN_PTR(Unsafe_SetObjectVolatile)},
DECLARE_GETSETOOP(Boolean, Z),
DECLARE_GETSETOOP(Byte, B),
DECLARE_GETSETOOP(Short, S),
DECLARE_GETSETOOP(Char, C),
DECLARE_GETSETOOP(Int, I), //
DECLARE_GETSETOOP(Long, J),
DECLARE_GETSETOOP(Float, F),
DECLARE_GETSETOOP(Double, D),
.....
.....
}
java的本地方法有个结构体JNINativeMethod,包括了方法名,方法签名,对应的c函数地址。所有的Unsafe类所有的本地方法都定义在了methods_18结构体数组里。getInt在DECLARE_GETSETOOP宏里。看DECLARE_GETSETOOP宏
//(char*)"getInt" Unsafe_GetInt
#define DECLARE_GETSETOOP(Boolean, Z) \
{CC"get"#Boolean, CC"("OBJ"J)"#Z, FN_PTR(Unsafe_Get##Boolean)}, \
{CC"put"#Boolean, CC"("OBJ"J"#Z")V", FN_PTR(Unsafe_Set##Boolean)}, \
{CC"get"#Boolean"Volatile", CC"("OBJ"J)"#Z, FN_PTR(Unsafe_Get##Boolean##Volatile)}, \
{CC"put"#Boolean"Volatile", CC"("OBJ"J"#Z")V", FN_PTR(Unsafe_Set##Boolean##Volatile)}
通过# 和##号达到复用类似的定义,可以看到getInt对应的c函数应该是Unsafe_GetInt。但是这个函数名也被宏定义了,并不是直接定义的,下面看函数定义:
#define JNICALL //在linux下为空
#define JVM_END } }
#define JVM_ENTRY(result_type, header) \
extern "C" { \
result_type JNICALL header { \
JavaThread* thread=JavaThread::thread_from_jni_environment(env); \
ThreadInVMfromNative __tiv(thread); \
debug_only(VMNativeEntryWrapper __vew;) \
VM_ENTRY_BASE(result_type, header, thread)
#define UNSAFE_ENTRY(result_type, header) \
JVM_ENTRY(result_type, header)
//这个宏会把jdk1.4到1.8所有版本的函数都定义一遍,只看1.8
#define DEFINE_GETSETOOP(jboolean, Boolean) \
UNSAFE_ENTRY(jboolean, Unsafe_Get##Boolean(JNIEnv *env, jobject unsafe, jobject obj, jlong offset)) \
UnsafeWrapper("Unsafe_Get"#Boolean); \
GET_FIELD(obj, offset, jboolean, v); \
return v; \
UNSAFE_END \
//Unsafe_GetInt函数定义
DEFINE_GETSETOOP(jint, Int);
用DEFINE_GETSETOOP定义了jdk1.4到1.8同名的函数,最终转换为
typedef int jint;
extern "C" {
jint Unsafe_GetInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset) {
JavaThread* thread=JavaThread::thread_from_jni_environment(env);
ThreadInVMfromNative __tiv(thread);
debug_only(VMNativeEntryWrapper __vew;)
VM_ENTRY_BASE(result_type, header, thread)
oop p = JNIHandles::resolve(obj);
jint v = *(jint*)index_oop_from_field_offset_long(p, offset)
return v;
}
}
是一个int类型的函数,下面主要看resolve和index_oop_from_field_offset_long函数
class _jobject {};
typedef _jobject *jobject;
//java对象头部描述符,从这个可以看出java对象头未开启指针压缩会占12字节,开启后占16字节
class oopDesc {
friend class VMStructs;
private:
volatile markOop _mark; //markOop是指针,64位占8字节
union _metadata {
Klass* _klass; //指针8字节
narrowKlass _compressed_klass; //压缩指针,unsigned int类型
} _metadata;
.......
.......
};
typedef class oopDesc* oop;
inline oop JNIHandles::resolve(jobject handle) {
//如果handle不为null则将handle从jobject类型转换为oop类型,oop类型
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;
};
resolve函数主要将handle从jobject类型转换为oop类型,也就是java对象头描述符。
//返回对象p的基地址加偏移
inline void* index_oop_from_field_offset_long(oop p, jlong field_offset) {
//获取偏移量
jlong byte_offset = field_offset_to_byte_offset(field_offset);
#ifdef ASSERT
if (p != NULL) {
assert(byte_offset >= 0 && byte_offset <= (jlong)MAX_OBJECT_SIZE, "sane offset");
if (byte_offset == (jint)byte_offset) {
void* ptr_plus_disp = (address)p + byte_offset;
assert((void*)p->obj_field_addr<oop>((jint)byte_offset) == ptr_plus_disp,
"raw [ptr+disp] must be consistent with oop::field_base");
}
jlong p_size = HeapWordSize * (jlong)(p->size());
assert(byte_offset < p_size, err_msg("Unsafe access: offset " INT64_FORMAT " > object's size " INT64_FORMAT, byte_offset, p_size));
}
#endif
//如果是32位机器,则将long long转换为int
if (sizeof(char*) == sizeof(jint)) // (this constant folds!)
return (address)p + (jint) byte_offset;
else
return (address)p + byte_offset; //返回对象p的基地址+上偏移
}
index_oop_from_field_offset_long返回java对象内的偏移地址。最后返回偏移地址存储的一个4字节int类型的是数据。getLong和getShort等等原理类似。
再看一个public native int getInt(long address);方法,直接从指定地址获取值
//public native int getInt(long address);对应的c函数
int Unsafe_GetNativeInt(JNIEnv *env, jobject unsafe, jlong addr) {
void* p = addr_from_java(addr); //将addr转换为void*指针
JavaThread* t = JavaThread::current(); //获取当前线程
t->set_doing_unsafe_access(true); //设置安全访问
java_type x = *(volatile native_type*)p; //获取地址p存储的值
t->set_doing_unsafe_access(false);
return x; //返回
}
其它这类函数原理一样。
再看Unsafe对内存的操作,public native long allocateMemory(long bytes);方法对应标准c的malloc函数,jvm对内存做一些校验后会直接调用malloc分配内存。public native long reallocateMemory(long address, long bytes);对应c的realloc函数,分配bytes字节内存,并且将以address为起始地址的数据复制到新分配的内存。
public native void setMemory(Object o, long offset, long bytes, byte value);类似与c的memset函数,将以o为起始地址,偏移为offset的地址处,填充bytes个value。
void Copy::fill_to_memory_atomic(void* to, size_t size, jubyte value) {
address dst = (address) to; //获取起始地址,to是java对象,起始地址必然是8字节对齐
uintptr_t bits = (uintptr_t) to | (uintptr_t) size; //获取结束地址
if (bits % sizeof(jlong) == 0) { //如果余数为0说明要填充整数个long long
jlong fill = (julong)( (jubyte)value );
if (fill != 0) {//将fill的8个字节,每个字节都变为value
fill += fill << 8;
fill += fill << 16;
fill += fill << 32;
}
//开始复制,复制size/sizeof(long long)个
for (uintptr_t off = 0; off < size; off += sizeof(jlong)) {
*(jlong*)(dst + off) = fill;
}
} else if (bits % sizeof(jint) == 0) { //和上类似
jint fill = (juint)( (jubyte)value ); // zero-extend
if (fill != 0) {
fill += fill << 8;
fill += fill << 16;
}
//Copy::fill_to_jints_atomic((jint*) dst, size / sizeof(jint));
for (uintptr_t off = 0; off < size; off += sizeof(jint)) {
*(jint*)(dst + off) = fill;
}
} else if (bits % sizeof(jshort) == 0) {//和上类似
jshort fill = (jushort)( (jubyte)value ); // zero-extend
fill += fill << 8;
//Copy::fill_to_jshorts_atomic((jshort*) dst, size / sizeof(jshort));
for (uintptr_t off = 0; off < size; off += sizeof(jshort)) {
*(jshort*)(dst + off) = fill;
}
} else { //如果复制的个数没和以上几个类型对齐,则调用fill_to_bytes,此函数调用了memset
// Not aligned, so no need to be atomic.
Copy::fill_to_bytes(dst, size, value);
}
再看一个比较有代表性的CAS操作,看看jvm是如何实现的,public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);以对象o为起始地址,偏移为offset处的值如果和expected相等,则将该处的值设置为x,返回true,否则不变返回false。
jboolean Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x) {
JavaThread* thread=JavaThread::thread_from_jni_environment(env);
ThreadInVMfromNative __tiv(thread);
debug_only(VMNativeEntryWrapper __vew;)
VM_ENTRY_BASE(result_type, header, thread)
UnsafeWrapper("Unsafe_CompareAndSwapInt");
oop p = JNIHandles::resolve(obj); //将obj转为oop类型
jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);//获取偏移地址
return (jint)(Atomic::cmpxchg(x, addr, e)) == e; //交换
}
//比较cmp $0,mp,如果mp是0,则跳到标号1,否则加上lock
#define LOCK_IF_MP(mp) "cmp $0, " #mp "; je 1f; lock; 1: "
//调用了intel的cmpxchg指令,如果是多核处理器则加lock前缀
inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {
int mp = os::is_MP(); //如果是多核返回1,否则返回0
__asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)" //判断是否是多核处理器,如果是加lock前缀
: "=a" (exchange_value)
: "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
: "cc", "memory");
return exchange_value;
}
在x86下最终也是调用的cmpxchg指令。并且如果是多核的话加上lock前缀,保证这条指令的原子性。cmpxchg的实现和linux kernel的实现差不多。cmpxchg指令已经在前面的博客详细分析过了,这里就不多说了。
下面再看public native int getIntVolatile(Object o, long offset);方法,获取int值但是加了个volatile,对应的c函数如下
//返回地址p的值,注意c/c++的volatile语义和java不同,volatile主要特性就是
//防止编译器优化将p地址的值缓冲到寄存器,防止编译器将变量访问打乱顺序,而java的volatile语义
//有内存屏障的作用
inline jint OrderAccess::load_acquire(volatile jint* p) { return *p; }
int Unsafe_GetIntVolatile(JNIEnv *env, jobject unsafe, jobject obj, jlong offset))
UnsafeWrapper("Unsafe_Get"#Boolean);
oop p = JNIHandles::resolve(obj); //将obj从jobject类型转换为oop类型
//获取偏移地址然后取值
volatile int v = OrderAccess::load_acquire((volatile int*)index_oop_from_field_offset_long(p, offset));
return v;
}
发现对地址的访问加了volatile,注意这里和java的volatile修饰符语义不一样,c/c++的volatile修饰符只是阻止编译器对变量进行优化,防止将地址p里的值缓冲的寄存器,而不是从cache或者内存读。volatile这个一般在操作IO寄存器或者多线程编程的时候有用。
再看 public native void putIntVolatile(Object o, long offset, int x);方法,在对象偏移offset处存储x。对应的c函数如下:
//xchg指令是两个寄存器内容交换,此处可以将*p的值给v,v的值给*p,为什么用xchg,不直接用mov
//因为这个操作保证store不会发生重排序,xchg指令是会自动加lock前缀的(可以查看intel官方手册)
//所以这个操作保证了原子性和起了内存屏障的作用
inline void OrderAccess::release_store_fence(volatile jint* p, jint v) {
__asm__ volatile ( "xchgl (%2),%0"
: "=r" (v)
: "0" (v), "r" (p)
: "memory");
}
void Unsafe_SetIntVolatile(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jboolean x)) {
oop p = JNIHandles::resolve(obj); //将obj转换为oop类型
//获取偏移地址并将值存到偏移地址处,这个存储不会重排序
OrderAccess::release_store_fence((volatile type_name*)index_oop_from_field_offset_long(p, offset), x);
}
这个操作有了内存屏障的作用,防止了内存重排序,底层使用了xchg指令,这个指令会自动加lock前缀,lock前缀不但具有原子性也具有屏障作用。
public native void putOrderedInt(Object o, long offset, int x);方法和 putIntVolatile一样。 putOrderedInt也是调用了Unsafe_SetIntVolatile函数。
#define SET_FIELD_VOLATILE(obj, offset, type_name, x) \
oop p = JNIHandles::resolve(obj); \
OrderAccess::release_store_fence((volatile type_name*)index_oop_from_field_offset_long(p, offset), x);
UNSAFE_ENTRY(void, Unsafe_SetOrderedInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint x))
UnsafeWrapper("Unsafe_SetOrderedInt");
SET_FIELD_VOLATILE(obj, offset, jint, x);
UNSAFE_END
然后再看下Unsafe类里的内存屏障,public native void loadFence();读内存屏障,load屏障调用了acquire函数。
//将寄存器栈顶值复制给局部变量,保证了编译器不会重排序,这里没使用lfence指令,因为
//x86不会发生read read重排序
inline void OrderAccess::acquire() {
volatile intptr_t local_dummy;
#ifdef AMD64
__asm__ volatile ("movq 0(%%rsp), %0" : "=r" (local_dummy) : : "memory");
#else
__asm__ volatile ("movl 0(%%esp),%0" : "=r" (local_dummy) : : "memory");
#endif // AMD64
}
没使用lfence,lfence是保证lfence之前所有的读操作完成之前,lfence之后的读操作不会越过屏障先读,由于x86 load load不会重排序,所以只需要保证编译器不会重排序指令即可,linux kernel下的读内存屏障smp_rmb和这个实现类似,lfence主要针对奔腾pro cpu使用的,奔腾pro有勘误表某些情况下可能会违反x86的标准内存序,所以使用lfence指令防止load load重排序,虽然都支持lfence指令,但是毕竟lfence指令开销大,所以除了奔腾pro处理器,其它处理器的读内存屏障操作,只需要防止编译器重排序就可以了。
public native void storeFence();方法jvm实现更简单,就一个赋值为0的操作,由于x86 store store不会重排序,所以store内存屏障不需要sfence保护。
最后看public native void fullFence();这个是全屏障,也就是不管读写都要遵守顺序,不能越过屏障执行。
//使用了lock前缀做内存屏障,add一个无用的操作,这样的方式比直接使用mfence指令效率高
inline void OrderAccess::fence() {
if (os::is_MP()) {
// always use locked addl since mfence is sometimes expensive
#ifdef AMD64
__asm__ volatile ("lock; addl $0,0(%%rsp)" : : : "cc", "memory");
#else
__asm__ volatile ("lock; addl $0,0(%%esp)" : : : "cc", "memory");
#endif
}
}
使用了lock前缀,保证了cpu对屏障前后的指令不会重排序,这里没使用mfence指令,是因为lock 加一条无意义的指令,要比mfence效率高,由于x86只会发生store load重排序,所以可以使用fullFence阻止这个重排序。
原文地址:https://blog.csdn.net/a7980718/article/details/83279728