美文网首页
Unsafe类初探

Unsafe类初探

作者: 菜鸟蚂蚁 | 来源:发表于2018-07-21 09:37 被阅读0次

    Unsafe类说明

    从这个类的名字Unsafe上来说这个类就是一个不安全的类,也是不开放给用户直接使用的(当然我们还是可以通过其他一些方法用到)。

    这个类在jdk源码中多个类中用到,主要作用是任意内存地址位置处读写数据,外加一下CAS操作。它的大部分操作都是绕过JVM通过JNI完成的,因此它所分配的内存需要手动free,所以是非常危险的。但是Unsafe中很多(但不是所有)方法都很有用,且有些情况下,除了使用JNI,没有其他方法弄够完成同样的事情。

    至于研究它的起因,是因为我最近在看jdk8的ConcurrentHashMap,这个版本的主要函数就是用过Unsafe来完成的。

    Unsafe类的调用

    Unsafe类是一个单例,调用的方法为getUnsafe,如下。可以看到,虽然是可以调用,但是会有一步判断,判断是不是内部会检查该CallerClass是不是由系统类加载器BootstrapClassLoader加载。由系统类加载器加载的类调用getClassLoader()会返回null,所以要检查类是否为bootstrap加载器加载只需要检查该方法是不是返回null。

    ```

    @CallerSensitive

    public static Unsafe getUnsafe() {

        Class var0 = Reflection.getCallerClass();

        if (!VM.isSystemDomainLoader(var0.getClassLoader())) {

            throw new SecurityException("Unsafe");

        }else {

        return theUnsafe;

        }

    }

    ```

    那我们怎么能调用到它呢,有以下2中方法。

    1、通过JVM参数-Xbootclasspath指定要使用的类为启动类;

    ```

    java -Xbootclasspath/a: ${path}   // 其中path为调用Unsafe相关方法的类所在jar包路径 .

    ```

    2、在Unsafe类中有一个成员变量theUnsafe,因此我们可以通过反射将private单例实例的accessible设置为true,然后通过Field的get方法获取,如下:

    ```

    Field f = Unsafe.class.getDeclaredField("theUnsafe");

    f.setAccessible(true);

    Unsafe unsafe = (Unsafe) f.get(null);

    ```

    不调用构造方法生成对象

    利用Unsafe的allocateInstance方法,可以在未调用构造方法的情况下生成了对象。下面的例子很好的说明了这一点。

    static class City {

    privateString name ="";

    privateintflag =0;

    public City() {

        this.name ="Beijing";

        this.flag =1;

     }

    @Override

    public String toString(){

                return name +": "+ flag;

          }

      }

    public static void main(String[] args) throwsException {

        // 通过反射得到theUnsafe对应的Field对象

        Field field = Unsafe.class.getDeclaredField("theUnsafe");

        // 设置该Field为可访问

        field.setAccessible(true);

        // 通过Field得到该Field对应的具体对象,传入null是因为该Field为static的

        Unsafe unsafe = (Unsafe) field.get(null);

          City city = (City) unsafe.allocateInstance(City.class);

        System.out.println(city);//dont invoke constructor, print null: 0

        City anotherCity =newCity();

        System.out.println(anotherCity);//print Beijing: 1

      }

    内存修改

    我们看一下下面的代码,在正常的情况下sizeValidate始终范围false,但是通过计算MAX_SIZE的位移,将其进行修改之后,就会返回true。

    static class Validation{

        private int MAX_SIZE =10;

        public boolean sizeValidate(){

            return 20< MAX_SIZE;

          }

      }

    public static void main(String[] args) throwsException{

    // 通过反射得到theUnsafe对应的Field对象

    Field field = Unsafe.class.getDeclaredField("theUnsafe");

    // 设置该Field为可访问

    field.setAccessible(true);

    // 通过Field得到该Field对应的具体对象,传入null是因为该Field为static的

    Unsafe unsafe = (Unsafe) field.get(null);

    Validation v =newValidation();

    System.out.println(v.sizeValidate());// false

    Field f = v.getClass().getDeclaredField("MAX_SIZE");

    unsafe.putInt(v, unsafe.objectFieldOffset(f),100);// memory corruption

    System.out.println(v.sizeValidate());// true

      }

    计算Java对象大小

    有两种计算Java对象大小的方式。

    通过java.lang.instrument.Instrumentation的getObjectSize(obj)直接获取对象的大小;

    通过sun.misc.Unsafe对象的objectFieldOffset(field)等方法结合反射来计算对象的大小。

    通过Unsafe获取Java对象大小的基本思路如下:

    通过反射获得一个类的Field;

    通过Unsafe的objectFieldOffset()获得每个Field的offSet;

    对Field进行遍历,取得最大的offset,然后加上这个field的长度,再加上Padding对齐。

    Java并发中的应用

    在Java并发中会用到CAS操作,对应于Unsafe类中的compareAndSwapInt,compareAndSwapLong等。下面的例子就是使用Unsafe实现的无所数据结构。

    class LongValue{

    private volatile long counter =0;

    private Unsafe unsafe;

    private long offset;

    public Long Value()throwsException{

              unsafe = getUnsafe();

                offset = unsafe.objectFieldOffset(LongValue.class.getDeclaredField("counter"));

          }

    public void increment(){

        long before = counter;

        while(!unsafe.compareAndSwapLong(this, offset, before, before +1)) {

                  before = counter;

              }

          }

            public long getCounter(){

            return counter;

          }

      }

    看一下Java中AtomicLong的实现,下面摘出来一部分。可以看到该类在加载的时候将value的偏移位置计算出来,然后在compareAndSet等方法中使用Unsafe中的CAS操作进行替换,这样的无锁操作可以大大提高效率。

    ```

    public class AtomicLong extends Number implements java.io.Serializable{

    private stati cfinal long serialVersionUID =1927816293512124184L;

    // setup to use Unsafe.compareAndSwapLong for updates

    private static final Unsafe unsafe = Unsafe.getUnsafe();

    private static final longvalueOffset;

        ...

    static{

    try{

                valueOffset = unsafe.objectFieldOffset

             (AtomicLong.class.getDeclaredField("value"));

                }catch(Exception ex) {thrownewError(ex); }

        }

            private volatile long value;

            ...

            public final boolean compareAndSet(longexpect,longupdate){

            return unsafe.compareAndSwapLong(this, valueOffset, expect, update);

        }

    ```

    其他使用方式

    通过Unsafe的defineClass可以动态加载Class;

    通过Unsafe的copyMemory、freeMemory等可以实现内存的复制与释放,如果我们知道了对象的大小,利用arrayBaseOffset和copyMemory可以完成对象的浅拷贝。

    Unsafe还有其他很多的用途,但是要记得这是一个非常危险的类,在使用的过程中需要万分小心,不到万不得已的情况不要使用。

    相关文章

      网友评论

          本文标题:Unsafe类初探

          本文链接:https://www.haomeiwen.com/subject/ymubsftx.html