Java8 Unsafe 解开你神秘的面纱

作者: 没有颜色的菜 | 来源:发表于2018-09-17 00:05 被阅读16次

前言

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 图


Screen Shot 2018-09-16 at 11.20.42 PM.png Screen Shot 2018-09-16 at 11.20.59 PM.png Screen Shot 2018-09-16 at 11.21.15 PM.png

太多了,眼睛都看瞎了
归个类吧

  • 直接操作内存,将某个对象的值更改,读取,比如将 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

相关文章

网友评论

    本文标题:Java8 Unsafe 解开你神秘的面纱

    本文链接:https://www.haomeiwen.com/subject/zarqnftx.html