前言
Unsafe 类一直是个很神秘的角色,我们普通开发者几乎不会碰到,顶多也是使用了并发包之类的系统类库,间接使用到了而已。那它到底是用来做什么的呢?它提供了我们直接操作内存的接口。Java 本身作为一个内存自动管理的工具,内存的开辟释放由虚拟机代为管理,然而,HotSpot 的设计者留下了 Unsafe 的类,用于扩展,它可以直接开辟内存,释放内存,读取任意地址的内存,而不受 Java 堆内存的限制。尽管如此,它并不能为我们所用,加载这个类只能由系统的类加载器执行,但我们可以通过反射获取到它的实例对象,Java 反射的确很牛啊。
如何获取实例对象
第一个问题:为什么我们需要通过 Field 获取,不能使用 newInstance 获取呢?
通过 newInstance 的方法获取实力需要构造函数是 public 的,否则会抛异常,及时 getUnsafe 是静态函数,我们也不能通过这个去获取,因为这时候类加载不是系统的,会抛异常
private Unsafe() {
}
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
Unsafe unsafe = Unsafe.class.newInstance();
由于 Unsafe 是单例,当类加载时,theUnsafe 实例会被加载,这样我们就可以通过反射获取这个实例
static {
registerNatives();
Reflection.registerMethodsToFilter(Unsafe.class, new String[]{"getUnsafe"});
// 重点
theUnsafe = new Unsafe();
}
通过 Field 获取
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafe.get(null);
}
获取到这实例,就可以干很多事情了.....
先来看看 API 图
太多了,眼睛都看瞎了
归个类吧
- 直接操作内存,将某个对象的值更改,读取,比如将 String 的 value 更改,是不是很神奇,这个是用反射也可以更改哦!下次面试的时候可以考考别人 String 的 value 怎么才能改的掉,看他会几种。
String str = "Hello Unsafe";
Field value = str.getClass().getDeclaredField("value");
unsafe.putObject(str, unsafe.objectFieldOffset(value), new char[]{'M', 'a', 'j', 'i', 'c'});]
System.out.println(str);
// output: Majic
// throw exception
String str = "Hello Unsafe";
Field value = str.getClass().getDeclaredField("value");
value.setAccessible(true);
value.set(str, new char[] {'f', 'i', 'n', 'a', 'l'});
- CompareAndSwap 著名的 CAS
为了保证并发安全, CAS 涉及到的变量应该使用 volatile 修饰,保证读到的值最新
allocateInstance 新建一个没有初始化的实例,各个值都是默认值
compareAndSwapLong 第一个参数是 object,第二个参数是变量内存偏移值,可以用 unsafe 类获取实际偏移值,第三个参数是 期望值,第四个三叔目标值,大致的语义就是:如果内存中是期望值,我就更新为目标值,否则不更新
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafe.get(null);
InnerClass o = (InnerClass)unsafe.allocateInstance(InnerClass.class);
o.print(); // print 100
Field a = o.getClass().getDeclaredField("value");
unsafe.putLong(o, unsafe.objectFieldOffset(a), 10000);
o.print(); // print 10000
unsafe.compareAndSwapLong(o, unsafe.objectFieldOffset(a), 10000, 1111);
o.print(); // print 1111
unsafe.compareAndSwapLong(o, unsafe.objectFieldOffset(a), 1000, 10000);
o.print(); // print 1111
}
static class InnerClass {
// 保证内存可见性
private volatile long value;
InnerClass() {
value = 100L;
}
void print() {
System.err.println("value==>" + value);
}
}
- 线程挂起,取消
使用 unsafe.unpark 可以取消 Thread.sleep() 后者 park 的线程
使用 unsafe.park(false, 1000000000) 可以挂起当前线程,第一个参数表示是isAbsolute,是否是绝对时间,后一个参数为时间,flase 表示相对时间,0表示一直挂起,单位为纳秒;true 表示绝对时间,单位为毫秒,System.currentThreadMillis 搭配使用
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException, InterruptedException {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafe.get(null);
InnerThread innerThread = new InnerThread(unsafe);
innerThread.run();
Thread.sleep(20);
// 取消挂起
unsafe.unpark(innerThread);
}
static class InnerThread extends Thread {
Unsafe unsafe;
InnerThread(Unsafe unsafe) {
this.unsafe = unsafe;
}
@Override
public void run() {
System.out.println("start");
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
//unsafe.park(false, 1000000000L);
System.out.println("end");
}
}
小结
以后又可以吹一波了,一箭双雕,反射,Unsafe