前两天在看Android平台上的FutureTask类的时候无意看到了Unsafe,当时这个类是报红色的属于sun.* API肿的类,并且不是J2SE中真正的一部分,因为你很有可能找不到任何的官方信息,但是你可以在Android源代码中可以找到该代码的实现以及 Native的实现,这个有利于我们的学习和使用。该类在Android4.4系统的Art虚拟机和Davilk虚拟中的代码中,其路径分别为:
1) libcore/libart/src/main/java/sun/misc/Unsafe.java
2) libcore/libdvm/src/main/java/sun/misc/Unsafe.java
我们平时都知道Java层的代码并不可以直接访问系统底层,这既保障了虚拟机的安全,又屏蔽了底层方便的差异化,可以很好的让程序在上层代码中去实现功能和业务逻辑。不过有时候我们需要直接像C那样子可以直接访问内存里面的内容并且修改其值,这个时候Unsafe类就在关键的时候可以更好的去实现了。Unsafe类提供了硬件级别的原子操作,主要提供了以下的功能:
- 通过Unsafe类可以分配内存
- 可以定位对象某字段的内存位置,并且可以随意的修改其值,只要该字段不是final类型
- 可以随意修改数组元素并且给数组赋值
- 线程的挂起与恢复
- CAS(Compare and Swap)操作
由于Unsafe类是一个比较特殊的,所以并没有出现在JDK文档中,其介绍也非常的好所以一般调用的时候也不会随随便便的让别人调用的,这里我们就需要通过的反射的方式来调用其对象,我们可以通过反射 getUnsafe()方法来获取对象,也可以通过获取 theUnsafe字段并且设置其访问的属性。从代码中我们看到了通过getSafe()方式直接获取Unsafe对象的时候都做了类加载器的判断。所以我们通过反射的方式来获取该对象是比较好的。
/** Traditional dalvik name. */
private static final Unsafe THE_ONE = new Unsafe();
/** Traditional RI name. */
//也可以通过反射该字段并且修改其访问属性为 true。
private static final Unsafe theUnsafe = THE_ONE;
private Unsafe() {}
// 这里我们可以通过反射的方法获取Unsafe的对象
public static Unsafe getUnsafe() {
/*
* Only code on the bootclasspath is allowed to get at the
* Unsafe instance.
*/
ClassLoader calling = VMStack.getCallingClassLoader();
if ((calling != null) && (calling != Unsafe.class.getClassLoader())) {
throw new SecurityException("Unsafe access denied");
}
return THE_ONE;
}
创建好了Unsafe对象之后,我们就可以使用 allocateInstance来给某个类创建一个对象,我们也可以说是分配内存空间
/**
* Allocates an instance of the given class without running the constructor.
* The class' <clinit> will be run, if necessary.
*/
public native Object allocateInstance(Class<?> c);
在创建完对象之后,首先我们就可以通过objectFieldOffset()
获取到该对象中的某个字段的内存地址。
/**
* Gets the raw byte offset from the start of an object's memory to
* the memory used to store the indicated instance field.
*
* @param field non-null; the field in question, which must be an
* instance field
* @return the offset to the field
*/
public long objectFieldOffset(Field field) {
if (Modifier.isStatic(field.getModifiers())) {
throw new IllegalArgumentException("valid for instance fields only");
}
return field.getOffset();
}
在C或者是C++代码中我们可以通过指针来获取某个对象的内存地址来操纵该对象的,在Unsafe中我们使用putInt()
来设置值,使用getInt()
来获取对象的字段值。
更新字段内容方法:
/**
* 通过字段的偏移地址给某个对象字段设置对象值,比如说给String类型的字段设置内容。
* 因为String是一个对象并且基本数值
*/
public native void putObject(Object obj, long offset, Object newValue);
/**
* 通过字段的偏移地址给某个对象字段设置对象值,比如说给String类型的字段设置内容。
* 因为String是一个对象并且基本数值
*/
public native void putOrderedObject(Object obj, long offset, Object newValue);
public native void putLong(Object obj, long offset, long newValue);
public native void putLongVolatile(Object obj, long offset, long newValue);
public native void putIntVolatile(Object obj, long offset, int newValue);
public native void putOrderedLong(Object obj, long offset, long newValue);
public native void putInt(Object obj, long offset, int newValue);
/**
* 通过字段的便宜地址给int对象字段设置值
* @param obj 需要设置内容对象值
* @param offset 字段的偏移地址
* @param newValue 需要更新的新值
*/
public native void putOrderedInt(Object obj, long offset, int newValue);
</