public final class Unsafe { // 单例对象 private static final Unsafe theUnsafe; // 堆代码 duidaima.com private Unsafe() { } @CallerSensitive public static Unsafe getUnsafe() { Class var0 = Reflection.getCallerClass(); // 仅在启动类加载器`BootstrapClassLoader`加载时才合法 if(!VM.isSystemDomainLoader(var0.getClassLoader())) { throw new SecurityException("Unsafe"); } else { return theUnsafe; } } }从源码中我们发现Unsafe类被final修饰,所以无法被继承,同时它的无参构造方法被private修饰,也无法通过new去直接实例化,不过在Unsafe 类提供了一个静态方法getUnsafe,看上去貌似可以用它来获取 Unsafe 实例。但是!当我们直接去调用这个方法的时候,会报如下错误:
Exception in thread "main" java.lang.SecurityException: Unsafe at sun.misc.Unsafe.getUnsafe(Unsafe.java:90) at com.cn.test.GetUnsafeTest.main(GetUnsafeTest.java:12)这是因为在getUnsafe方法中,会对调用者的classLoader进行检查,判断当前类是否由Bootstrap classLoader加载,如果不是的话就会抛出一个SecurityException异常。那我们如果想使用Unsafe类,到底怎样才能获取它的实例呢?
java -Xbootclasspath/a: ${path} // 其中path为调用Unsafe相关方法的类所在jar包路径方式二
public static Unsafe getUnsafe() throws IllegalAccessException { Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe"); //Field unsafeField = Unsafe.class.getDeclaredFields()[0]; //也可以这样,作用相同 unsafeField.setAccessible(true); Unsafe unsafe =(Unsafe) unsafeField.get(null); return unsafe; }2.2 Unsafe的使用
public class TestService { //通过单例获取实例 public static Unsafe getUnsafe() throws IllegalAccessException, NoSuchFieldException { Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe"); //Field unsafeField = Unsafe.class.getDeclaredFields()[0]; //也可以这样,作用相同 unsafeField.setAccessible(true); Unsafe unsafe =(Unsafe) unsafeField.get(null); return unsafe; } //堆代码 duidaima.com //调用实例方法去赋值 public void fieldTest(Unsafe unsafe) throws NoSuchFieldException { Persion persion = new Persion(); persion.setAge(10); System.out.println("ofigin_age:" + persion.getAge()); long fieldOffset = unsafe.objectFieldOffset(Persion.class.getDeclaredField("age")); System.out.println("offset:"+fieldOffset); unsafe.putInt(persion,fieldOffset,20); System.out.println("new_age:"+unsafe.getInt(persion,fieldOffset)); } public static void main(String[] args) { TestService testService = new TestService(); try { testService.fieldTest(getUnsafe()); } catch (NoSuchFieldException | IllegalAccessException e) { e.printStackTrace(); } } } class Persion{ private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }输出:
ofigin_age:10 offset:12 new_age:20通过 Unsafe 类的objectFieldOffset方法获取到了对象中字段的偏移地址,这个偏移地址不是内存中的绝对地址而是一个相对地址,之后再通过这个偏移地址对int类型字段的属性值进行读写操作,通过结果也可以看到 Unsafe 的方法和类中的get方法获取到的值是相同的。
/*包含堆外内存的分配、拷贝、释放、给定地址值操作*/ //分配内存, 相当于C++的malloc函数 public native long allocateMemory(long bytes); //扩充内存 public native long reallocateMemory(long address, long bytes); //释放内存 public native void freeMemory(long address); //在给定的内存块中设置值 public native void setMemory(Object o, long offset, long bytes, byte value); //内存拷贝 public native void copyMemory(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes); //获取给定地址值,忽略修饰限定符的访问限制。与此类似操作还有: getInt,getDouble,getLong,getChar等 public native Object getObject(Object o, long offset); //为给定地址设置值,忽略修饰限定符的访问限制,与此类似操作还有: putInt,putDouble,putLong,putChar等 public native void putObject(Object o, long offset, Object x); //获取给定地址的byte类型的值(当且仅当该内存地址为allocateMemory分配时,此方法结果为确定的) public native byte getByte(long address); //为给定地址设置byte类型的值(当且仅当该内存地址为allocateMemory分配时,此方法结果才是确定的) public native void putByte(long address, byte x);在这里我们不仅会想,为啥全是native方法呢?
从上图我们可以看到,在构建实例时,DirectByteBuffer内部通过Unsafe.allocateMemory分配内存、Unsafe.setMemory进行内存初始化,而后构建Cleaner对象用于跟踪DirectByteBuffer对象的垃圾回收,以实现当DirectByteBuffer被垃圾回收时,分配的堆外内存一起被释放。
//内存屏障,禁止load操作重排序。屏障前的load操作不能被重排序到屏障后,屏障后的load操作不能被重排序到屏障前 public native void loadFence(); //内存屏障,禁止store操作重排序。屏障前的store操作不能被重排序到屏障后,屏障后的store操作不能被重排序到屏障前 public native void storeFence(); //内存屏障,禁止load、store操作重排序 public native void fullFence();【经典应用】
/** * 使用乐观读锁访问共享资源 * 注意:乐观读锁在保证数据一致性上需要拷贝一份要操作的变量到方法栈,并且在操作数据时候 可能其他写线程已经修改了数据, * 而我们操作的是方法栈里面的数据,也就是一个快照,所以最多返回的不是最新的数据,但是一致性还是得到保障的。 * * @return */ double distanceFromOrigin() { long stamp = sl.tryOptimisticRead(); // 获取乐观读锁 double currentX = x, currentY = y; // 拷贝共享资源到本地方法栈中 if (!sl.validate(stamp)) { // //检查乐观读锁后是否有其他写锁发生,有则返回false stamp = sl.readLock(); // 获取一个悲观读锁 try { currentX = x; currentY = y; } finally { sl.unlockRead(stamp); // 释放悲观读锁 } } return Math.sqrt(currentX * currentX + currentY * currentY); }在官网给出的乐观读的使用案例中,我们看到if中做了一个根绝印章校验写锁发生的操作,我们跟入这个校验源码中:
public boolean validate(long stamp) { U.loadFence();//load内存屏障 return (stamp & SBITS) == (state & SBITS); }这一步的目的是防止锁状态校验运算发生重排序导致锁状态校验不准确的问题!
//绕过构造方法、初始化代码来创建对象 public native Object allocateInstance(Class<?> cls) throws InstantiationException;这种方法可以绕过构造方法和初始化代码块来创建对象,我们写一个小demo学习一下。
@Data public class A { private int b; public A(){ this.b =1; } }定义一个类A,我们分别采用无参构造器、newInstance()、Unsafe方法进行实例化。
public void objTest() throws Exception{ A a1=new A(); System.out.println(a1.getB()); A a2 = A.class.newInstance(); System.out.println(a2.getB()); A a3= (A) unsafe.allocateInstance(A.class); System.out.println(a3.getB()); }输出结果为1,1,0。这说明调用unsafe的allocateInstance方法确实可以跳过构造器去实例化对象!
//获取数组元素在内存中的偏移地址,以及偏移量 private void arrayTest(Unsafe unsafe) { String[] array=new String[]{"aaa","bb","cc"}; int baseOffset = unsafe.arrayBaseOffset(String[].class); System.out.println("数组第一个元素的偏移地址:" + baseOffset); int scale = unsafe.arrayIndexScale(String[].class); System.out.println("元素偏移量" + scale); for (int i = 0; i < array.length; i++) { int offset=baseOffset+scale*i; System.out.println(offset+" : "+unsafe.getObject(array,offset)); } }输出:
public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }CAS思想的底层实现其实就是Unsafe类中的几个native本地方法:
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5); public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5); public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);3.6 线程调度
//取消阻塞线程 public native void unpark(Object thread); //阻塞线程 public native void park(boolean isAbsolute, long time); //获得对象锁(可重入锁) @Deprecated public native void monitorEnter(Object o); //释放对象锁 @Deprecated public native void monitorExit(Object o); //尝试获取对象锁 @Deprecated public native boolean tryMonitorEnter(Object o);
public static void park(Object blocker) { Thread t = Thread.currentThread(); setBlocker(t, blocker); UNSAFE.park(false, 0L); setBlocker(t, null); } public static void unpark(Thread thread) { if (thread != null) UNSAFE.unpark(thread); }3.7 Class操作
//获取给定静态字段的内存地址偏移量,这个值对于给定的字段是唯一且固定不变的 public native long staticFieldOffset(Field f); //获取一个静态类中给定字段的对象指针 public native Object staticFieldBase(Field f); //判断是否需要初始化一个类,通常在获取一个类的静态属性的时候(因为一个类如果没初始化,它的静态属性也不会初始化)使用。 当且仅当ensureClassInitialized方法不生效时返回false。 public native boolean shouldBeInitialized(Class<?> c); //检测给定的类是否已经初始化。通常在获取一个类的静态属性的时候(因为一个类如果没初始化,它的静态属性也不会初始化)使用。 public native void ensureClassInitialized(Class<?> c); //定义一个类,此方法会跳过JVM的所有安全检查,默认情况下,ClassLoader(类加载器)和ProtectionDomain(保护域)实例来源于调用者 public native Class<?> defineClass(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain); //定义一个匿名类 public native Class<?> defineAnonymousClass(Class<?> hostClass, byte[] data, Object[] cpPatches);【测试案例】
@Data public class User { public static String name="javabuild"; int age; } private void staticTest() throws Exception { User user=new User(); //判断是否需要初始化一个类,通常在获取一个类的静态属性的时候(因为一个类如果没初始化,它的静态属性也不会初始化)使用 System.out.println(unsafe.shouldBeInitialized(User.class)); Field sexField = User.class.getDeclaredField("name"); //获取给定静态字段的内存地址偏移量 long fieldOffset = unsafe.staticFieldOffset(sexField); //获取一个静态类中给定字段的对象指针 Object fieldBase = unsafe.staticFieldBase(sexField); //根据某个字段对象指针和偏移量可以唯一定位这个字段。 Object object = unsafe.getObject(fieldBase, fieldOffset); System.out.println(object); }此外,在Java8中引入的Lambda表达式的实现中也使用到了defineClass和defineAnonymousClass方法。
private void systemTest() { System.out.println(unsafe.addressSize()); System.out.println(unsafe.pageSize()); }输出为:8,4096