Unsafe及CAS原理

本文详细介绍了Java中的Unsafe类,包括其提供的CAS、内存操作等方法,以及如何通过反射获取Unsafe实例。文章还探讨了Unsafe的使用场景,并展示了Unsafe在CAS操作中的底层C++实现。通过对HotSpot JVM源码的分析,揭示了Unsafe的内部工作机制。
摘要由CSDN通过智能技术生成

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAeG1oLXN4aC0xMzE0,size_20,color_FFFFFF,t_70,g_se,x_16JavaDoc说, 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: "

替换文

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值