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