Unsafe类初探

2018-07-21  本文已影响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还有其他很多的用途,但是要记得这是一个非常危险的类,在使用的过程中需要万分小心,不到万不得已的情况不要使用。

上一篇 下一篇

猜你喜欢

热点阅读