- Unsafe类 不能直接 new, 其构造函数被私有化
-
public final class Unsafe
: Unsafe 是 final 的, 不能被继承 - 方法:
- native 的 compareAndSwap 方法, 实现 CAS 比较交换
- native 的 put/get volatile 类型变量
- native 的 park/unpark ,实现加锁解锁
- java 本地方法的 getAndSet 和 getAndAdd, 内部调用了 native 的 compareAndSwap
-
Unsafe 之所以叫做不安全的, 是因为他可以直接操作内存地址, 修改对象属性
-
Unsafe 暴露了一个静态方法用于获取对象, 但会校验加载 Unsafe 的是否为
Bootstrap ClassLoader
, 不是则抛异常public static Unsafe getUnsafe() { Class var0 = Reflection.getCallerClass(); if (!VM.isSystemDomainLoader(var0.getClassLoader())) { throw new SecurityException("Unsafe"); } else { return this.theUnsafe; } } // 静态代码块 static { registerNatives(); Reflection.registerMethodsToFilter(Unsafe.class, new String[]{"getUnsafe"}); theUnsafe = new Unsafe(); ARRAY_BOOLEAN_BASE_OFFSET = theUnsafe.arrayBaseOffset(boolean[].class); ARRAY_BYTE_BASE_OFFSET = theUnsafe.arrayBaseOffset(byte[].class); ... ... ... ADDRESS_SIZE = theUnsafe.addressSize(); }
因此, 如果想获取 Unsafe 实例, 只能通过反射. 看到 getUnsafe
返回 theUnsafe 属性, 且改属性在静态代码块中已被初始化. 即只要该类加载, 该类的实例就被存在属性中, 我们直接反射获取属性即可
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;
}
二. 功能实现
-
内存操作
实现像 C++ 一样的申请, 释放, 调整, 拷贝内存. 通过 Unsafe 申请的内存是对外内存, 需要手动释放. 方法为在 finally 中释放申请的内存- 方法列表
// 分配新的本地空间 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 native void copyMemory(Object srcBase, long srcOffset,Object destBase, long destOffset,long bytes); // 清除内存 public native void freeMemory(long address);
- 示例
private static void memoryTest(Unsafe unsafe) { int size = 4; // 分配4字节长度的内存 long addr = unsafe.allocateMemory(size); // 分配一个8字节长度的内存 long addr3 = unsafe.reallocateMemory(addr, size * 2); System.out.println("addr地址: "+addr); System.out.println("addr3地址: "+addr3); try { // 初始化内存, 向4个字节中的每个字节, 一次写入 byte(1). 即内存中为 0001 0001 0001 0001 => 组成的4字节 int 型值为 16843009 unsafe.setMemory(null,addr ,size,(byte)1); // 从 addr 想 addr3 拷贝值, 因为 addr3 比 addr 长1倍, 所以需要拷贝2次 // 拷贝后, addr3 中的内容为 0001 0001 0001 0001 0001 0001 0001 0001 => long 型值为 72340172838076673 for (int i = 0; i < 2; i++) { unsafe.copyMemory(null,addr,null,addr3+size*i,4); } System.out.println(unsafe.getInt(addr)); System.out.println(unsafe.getLong(addr3)); }finally { // 手动释放内存, Unsafe的内存不在 jvm 中 unsafe.freeMemory(addr); unsafe.freeMemory(addr3); } }
-
内存屏障
- Unsafe 提供了防止指令重排的方法. 分为读重排, 写重排, 读写重排. 内存屏障相当于一个操作的同步点, 确保同步点之前的指令不会被重排到同步点之后
public native void loadFence(); public native void storeFence(); public native void fullFence();
- 以
loadFence
为例, 他会禁止读操作重排序, 确保该点之前的所有读操作都已完成, 且会将线程从主内存拷贝过来的缓存值设为无效, 重新从主内存中加载数据 - 示例, 我们用 Unsafe 的读屏障, 模拟 volatile
// 定义一个线程方法,在线程中去修改flag标志位,注意这里的flag是没有被volatile修饰的: class ChangeThread implements Runnable{ /**volatile**/ boolean flag=false; @Override public void run() { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("subThread change flag to:" + flag); flag = true; } } // 主线程用读屏障, 确保变量的可见性 public static void main(String[] args){ ChangeThread changeThread = new ChangeThread(); new Thread(changeThread).start(); while (true) { boolean flag = changeThread.isFlag(); unsafe.loadFence(); //加入读内存屏障 if (flag){ System.out.println("detected flag changed"); break; } } System.out.println("main thread end"); }
-
对象操作
-
修改属性值
- Unsafe 可以通过计算某个属性在对象内中的偏移量, 直接修改内存地址上的值, 达到修改对象属性的目的
/** * 获取属性的内存地址修改对象属性值 */ public static void fieldTest(Unsafe unsafe) throws NoSuchFieldException { User user=new User(); long fieldOffset = unsafe.objectFieldOffset(User.class.getDeclaredField("age")); System.out.println("内存地址offset:"+fieldOffset); unsafe.putInt(user,fieldOffset,20); System.out.println("age:"+unsafe.getInt(user,fieldOffset)); System.out.println("age:"+user.getAge()); }
- Unsafe 的操作属性修改有三类: volatile读写, 有序写入, 普通读写
// 在对象的指定偏移地址获取一个对象引用 // (1) 普通读写 public native Object getObject(Object o, long offset); // 在对象指定偏移地址写入一个对象引用 public native void putObject(Object o, long offset, Object x); // (2) volatile 读写 //在对象的指定偏移地址处读取一个int值,支持volatile load语义 public native int getIntVolatile(Object o, long offset); //在对象指定偏移地址处写入一个int,支持volatile store语义 public native void putIntVolatile(Object o, long offset, int x); // (3) 顺序读写 public native void putOrderedObject(Object o, long offset, Object x); public native void putOrderedInt(Object o, long offset, int x); public native void putOrderedLong(Object o, long offset, long x);
- 从实现效果上看
- 有序写入: 保证写入时的有序性,而不保证可见性,也就是一个线程写入的值不能保证其他线程立即可见。
- volatile写入: 保证可见性和有序性。get 和 set 会同步操作主存. 在执行get操作时,强制从主存中获取属性值; 在使用put方法设置属性值时,会强制将值更新到主存中,从而保证这些变更对其他线程是可见的。
-
初始化对象
- Unsafe 的
allocateInstance
方法,允许我们使用非常规的方式进行对象的实例化. 不通过构造函数初始化对象, 通过对象及其属性的字段长度初始化 - Unsafe 的
allocateInstance
初始化对象, 只能将属性设置为每种数据类型的默认值
public class A { private int b; public A(){ this.b =1; } } 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()); }
- Unsafe 的
-
-
数组操作 (略)
-
CAS
在 Unsafe 类中,提供了compareAndSwapObject
、compareAndSwapInt
、compareAndSwapLong
方法来实现的对 Object、int、long 类型的CAS操作public final native boolean compareAndSwapInt(Object o, long offset,int expected,int x);
-
compareAndSwapInt
方法名: 表示 CAS 更新对象中的 int 属性 -
Object o
: 待更新的对象 -
long offset
: 待更新字段的地址偏移量 -
int expected,int x
: 表示只有字段值为 expected 时, 才把字段值改为 x
如下示例 , 输出 1 2 3 4 5 6 7 8 9
public class Test { private volatile int a; public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException { Test Test=new Test(); Unsafe unsafe = Test.getUnsafe(); // 负责 [1,5) 的累加 new Thread(()->{ for (int i = 1; i < 5; i++) { Test.increment(i,unsafe); System.out.print(Test.a+" "); } }).start(); // 负责 [5,10] 的累加 new Thread(()->{ for (int i = 5 ; i <10 ; i++) { Test.increment(i,unsafe); System.out.print(Test.a+" "); } }).start(); } private void increment(int x, Unsafe unsafe){ while (true){ try { // 只有在a的值等于传入的参数x减一时,才会将a的值变为x long fieldOffset = unsafe.objectFieldOffset(Test.class.getDeclaredField("a")); if (unsafe.compareAndSwapInt(this,fieldOffset,x-1,x)) break; else Thread.yield(); } catch (NoSuchFieldException e) { e.printStackTrace(); } } } }
-
-
线程调度
AQS 经常使用LockSupport
挂起或唤醒指定线程. LockSupport 的park
方法调用了Unsafe 的 park 方法来阻塞当前线程,此方法将线程阻塞后就不会继续往后执行,直到有其他线程调用unpark
方法唤醒当前线程。-
unpark
方法是其他线程调用, 唤醒本线程的 -
park
方法是本线程调用, 自己阻塞自己
下面一个例子对Unsafe的这两个方法进行测试:
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException { Thread mainThread = Thread.currentThread(); Unsafe unsafe = Test.getUnsafe(); new Thread(()->{ try { TimeUnit.SECONDS.sleep(5); System.out.println("subThread try to unpark mainThread"); // 新线程唤醒主线程 unsafe.unpark(mainThread); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); System.out.println("park main mainThread"); // 主线程自己阻塞自己 unsafe.park(false,0L); System.out.println("unpark mainThread success"); }
-
-
对 Class 操作
Unsafe 对 Class 的相关操作主要包括类加载和静态变量的操作方法。- 加载静态变量
- 前面提到 unsafe 可以操作对象的属性, 但静态属性和对象没关系, 和类有关系; 静态属性单独存在方法区, jdk1.8 后静态属性存在堆中, 但仍与普通对象在不同区域. 因此操作静态属性的是新的 api
//获取静态属性的偏移量 public native long staticFieldOffset(Field f); //获取静态属性的对象指针 public native Object staticFieldBase(Field f); //判断类是否需要实例化(用于获取类的静态属性前进行检测) // 如果 jvm 已加载 Class c , 则返回 false, 否则返回 true public native boolean shouldBeInitialized(Class<?> c);
- 测试示例
上面示输出 false, Hydraprivate static void staticTest(Unsafe unsafe) throws Exception { User user = new User(); // 为了让 jvm 加载类, 从而初始化静态字段. 下面那句和这句目的一样 // Class.forName(User.class.getName()); System.out.println(unsafe.shouldBeInitialized(User.class)); // 反射字段 Field sexField = User.class.getDeclaredField("name"); // 获取静态字段 "name" 对象 Object fieldBase = unsafe.staticFieldBase(sexField); // 获取静态字段 "name" 所在方法区/堆 中的地址 long fieldOffset = unsafe.staticFieldOffset(sexField); Object object = unsafe.getObject(fieldBase, fieldOffset); System.out.println(object); } class User { public static String name = "Hydra"; int age; }
如果注释掉User user = new User();
, 输出 true , null
- 加载类
public native Class<?> defineClass(String name, byte[] b, int off, int len, ClassLoader loader,ProtectionDomain protectionDomain);
- 加载静态变量
-
系统信息
Unsafe 中提供的addressSize
和pageSize
方法用于获取系统信息-
addressSize
方法会返回系统指针的大小,如果在64位系统下默认会返回8,而32位系统则会返回4。 -
pageSize
方法会返回内存页的大小,值为2的整数幂。
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException { Unsafe unsafe = Test.getUnsafe(); System.out.println(unsafe.addressSize()); // 8 (64位系统, 地址长度为8字节; 32位系统, 地址长度为4字节) System.out.println(unsafe.pageSize()); // 4 (内存页大小: 4kb) }
-
网友评论