Unsafe(sun.misc.Unsafe)是jdk中自带的一个类,因为是在sun.misc包下,所以一般也不建议直接使用,同时Unsafe提供了一组底层(low-level)、unsafe的操作。Unsafe类注释中也有这样一段描述:
虽然该类及所有方法都是公开的,但使用该类是有限的,因为只有受信任的代码才能获得它的实例。
Unsafe对象不能直接通过构造函数创建,因为Unsafe被设计为单例模式。也不能通过getUnsafe方法获取,因为getUnsafe被设计成只能从引导类加载器(bootstrap class loader)加载。下面是getUnsafe方法代码:
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
Unsafe类中的theUnsafe静态成员变量:private static final Unsafe theUnsafe;
没错就是自身的引用。并且在源码中存在一段static代码块:
static {
...
theUnsafe = new Unsafe();
...
}
也就是说该theUnsafe静态成员变量在Unsafe第一次使用时就已经初始化。因此可以通过反射的方式来访问Unsafe类中的theUnsafe静态成员变量。
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
Unsafe的大部分API都是native的方法,主要包括以下几类:
看一个例子来体会一下Unsafe的操作对象
public class UnsafeUser {
public static void main(String[] args) throws Exception {
//通过反射实例化Unsafe
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
//实例化User
User player = (User) unsafe.allocateInstance(User.class);
player.setName("four you");
System.out.println(player.getName());
}
}
class User{
private String name;
private User(){
System.out.println("Constracter userd");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
输出的结果为:four you,注意并没有将构造函数中的语句执行。
allocateInstance方法作用是创建对象,但并不会调用其构造方法。如果类未被初始化,将初始化类。看到作者表示一脸蒙蔽,经仔细回想普通创建对象的方式new或者通过反射机制都是需要调用构造函数的也就是new的操作,在字节码层面会执行三条指令:
根据类型分配一块内存区域
第一条指令返回的内存地址压入操作数栈顶
调用类的构造函数
allocateInstance(Class)方法只做了第一步和第二步,即分配内存空间,返回内存地址,没有做第三步调用构造函数。所以Unsafe.allocateInstance()方法创建的对象都是只有初始值,没有默认值也没有构造函数设置的值,因为它完全没有使用new机制,直接操作内存创建了对象。
对象和类相关API还有:
//获得对象的字段偏移量
public native long objectFieldOffset(Field f);
//获得给定对象地址偏移量的int值
public native int getInt(Object o, long offset);
//设置给定对象地址偏移量的int值
public native void putInt(Object o, long offset, int x);
//静态属性的偏移量,用于在对应的Class对象中读写静态属性
public native long staticFieldOffset(Field f);
//获得给定对象地址偏移量的int值
public native int getInt(Object o, long offset);
//设置给定对象地址偏移量的int值
public native void putInt(Object o, long offset, int x);
//获取数组第一个元素的偏移地址
public native int arrayBaseOffset(Class arrayClass);
//数组中一个元素占据的内存空间,arrayBaseOffset与arrayIndexScale配合使用,可定位数组中每个元素在内存中的位置
public native int arrayIndexScale(Class arrayClass);
练习一下:
public class UnsafeUser{
public static void main(String[] args) throws Exception {
//通过反射实例化Unsafe
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
Field f1 = User.class.getDeclaredField("name");
Field f2 = User.class.getDeclaredField("age");
//获取普通变量的偏移量
System.out.println(unsafe.objectFieldOffset(f1));
//获取静态变量的偏移量
System.out.println(unsafe.staticFieldOffset(f2));
}
}
class User {
private static Integer age = 22;
private String name;
private User() {
System.out.println("Constracter userd");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public static Integer getAge() {
return age;
}
}
类似的操作类和对象的方法还有很多,不在此处一一举例了。
3.1 CAS相关
CAS:CompareAndSwap,内存偏移地址offset,预期值expected,新值x。如果变量在当前时刻的值和预期值expected相等,尝试将变量的值更新为x。如果更新成功,返回true;否则,返回false。原子类的底层就是调用Unsafe的CAS方法,而unsafe的底层是通过cpu的原子指令来实现。
//第一个参数o为给定对象,offset为对象内存的偏移量,通过这个偏移量迅速定位字段并设置或获取该字段的值,
//expected表示期望值,x表示要设置的值,下面3个方法都通过CAS原子指令执行操作。
public final native boolean compareAndSwapObject(Object o, long offset,Object expected, Object x);
public final native boolean compareAndSwapInt(Object o, long offset,int expected,int x);
public final native boolean compareAndSwapLong(Object o, long offset,long expected,long x);
JDK 1.8新增的几个方法,它们的实现是基于上述的CAS方法,如下
//1.8新增,给定对象o,根据获取内存偏移量指向的字段,将其增加delta,
//这是一个CAS操作过程,直到设置成功方能退出循环,返回旧值
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
//获取内存中最新值
v = getIntVolatile(o, offset);
//通过CAS操作
} while (!compareAndSwapInt(o, offset, v, v + delta));
return v;
}
//1.8新增,方法作用同上,只不过这里操作的long类型数据
public final long getAndAddLong(Object o, long offset, long delta) {
long v;
do {
v = getLongVolatile(o, offset);
} while (!compareAndSwapLong(o, offset, v, v + delta));
return v;
}
//1.8新增,给定对象o,根据获取内存偏移量对于字段,将其 设置为新值newValue,
//这是一个CAS操作过程,直到设置成功方能退出循环,返回旧值
public final int getAndSetInt(Object o, long offset, int newValue) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, newValue));
return v;
}
// 1.8新增,同上,操作的是long类型
public final long getAndSetLong(Object o, long offset, long newValue) {
long v;
do {
v = getLongVolatile(o, offset);
} while (!compareAndSwapLong(o, offset, v, newValue));
return v;
}
//1.8新增,同上,操作的是引用类型数据
public final Object getAndSetObject(Object o, long offset, Object newValue) {
Object v;
do {
v = getObjectVolatile(o, offset);
} while (!compareAndSwapObject(o, offset, v, newValue));
return v;
}
将一个线程进行挂起是通过park方法实现的,调用 park后,线程将一直阻塞直到超时或者中断等条件出现。unpark可以终止一个挂起的线程,使其恢复正常。Java对线程的挂起操作被封装在 LockSupport类中,LockSupport类中有各种版本pack方法,其底层实现最终还是使用Unsafe.park()方法和Unsafe.unpark()方法
//线程调用该方法,线程将一直阻塞直到超时,或者是中断条件出现。
public native void park(boolean isAbsolute, long time);
//终止挂起的线程,恢复正常.java.util.concurrent包中挂起操作都是在LockSupport类实现的,其底层正是使用这两个方法,
public native void unpark(Object thread);
3.3volatile相关读写
Java中的基本类型(boolean、byte、char、short、int、long、float、double)及对象引用类型都有以下方法。
//从对象的指定偏移量处获取变量的引用,使用volatile的加载语义
//相当于getObject(Object, long)的volatile版本
public native Object getObjectVolatile(Object o, long offset);
//存储变量的引用到对象的指定的偏移量处,使用volatile的存储语义
//相当于putObject(Object, long, Object)的volatile版本
public native void putObjectVolatile(Object o, long offset, Object x);
allocateMemory所分配的内存需要手动free(不被GC回收)
//(boolean、byte、char、short、int、long、float、double)都有以下get、put两个方法。
//获得给定地址上的int值
public native int getInt(long address);
//设置给定地址上的int值
public native void putInt(long address, int x);
//获得本地指针
public native long getAddress(long address);
//存储本地指针到给定的内存地址
public native void putAddress(long address, long x);
//分配内存
public native long allocateMemory(long bytes);
//重新分配内存
public native long reallocateMemory(long address, long bytes);
//初始化内存内容
public native void setMemory(Object o, long offset, long bytes, byte value);
//初始化内存内容
public void setMemory(long address, long bytes, byte value) {
setMemory(null, address, bytes, value);
}
//内存内容拷贝
public native void copyMemory(Object srcBase, long srcOffset,
Object destBase, long destOffset,
long bytes);
//内存内容拷贝
public void copyMemory(long srcAddress, long destAddress, long bytes) {
copyMemory(null, srcAddress, null, destAddress, bytes);
}
//释放内存
public native void freeMemory(long address);
操做这些API感觉像是在写C++…
还有一些:
//返回指针的大小。返回值为4或8。
public native int addressSize();
/** The value of {@code addressSize()} */
public static final int ADDRESS_SIZE = theUnsafe.addressSize();
//内存页的大小。
public native int pageSize();
引用一段话结束本文:
在Java中使用本地内存有它的意义。从延迟的角度来说,直接访问本地内存不会比访问Java堆快。这个结论其实是有道理的,因为跨越JVM屏障肯定是有开销的。这样的结论对使用本地还是堆的ByteBuffer同样适用。使用本地ByteBuffer的速度提升不在于访问这些内存,而是它可以直接与操作系统提供的本地IO进行操作。