美文网首页
J.U.C之Unsafe: Unsafe 创建和运用场景

J.U.C之Unsafe: Unsafe 创建和运用场景

作者: 贪睡的企鹅 | 来源:发表于2019-07-03 11:27 被阅读0次

    1 创建Unsafe

    Unsafe 对象不能直接通过 new Unsafe() 或调用 Unsafe.getUnsafe() 获取

    //Unsafe 被设计成单例模式,构造方法私有。
    private Unsafe() {
    }
    
    //getUnsafe 被设计成只能从引导类加载器(bootstrap class loader)加载。
    //非启动类加载器直接调用 Unsafe.getUnsafe() 方法会抛出 SecurityException 异常。
    public static Unsafe getUnsafe() {
            Class var0 = Reflection.getCallerClass(2);
            if (var0.getClassLoader() != null) {
                throw new SecurityException("Unsafe");
            } else {
                return theUnsafe;
            }
    }
    

    因而放我们想创建一个Unsafe有2种方式

    1 可以令代码 " 受信任 "。运行程序时,通过 JVM 参数设置 bootclasspath 选项,指定系统类路径加上使用的一个 Unsafe 路径。

    java -Xbootclasspath:/usr/jdk1.7.0/jre/lib/rt.jar:. com.mishadoff.magic.UnsafeClient
    

    2 通过 Java 反射机制。

    Field f = Unsafe.class.getDeclaredField("theUnsafe");
    f.setAccessible(true);
    Unsafe unsafe = (Unsafe) f.get(null);
    

    在 IDE 中,这些方法会被标志为 Error,可以通过以下设置解决:

    Preferences -> Java -> Compiler -> Errors/Warnings -> Deprecated 
    and restricted API -> Forbidden reference -> Warning
    

    2 Unsafe类的使用场景

    2.1 创建但并不初始化类实例

    当想要绕过对象构造方法、安全检查器或者没有 public 的构造方法时,使用allocateInstance()方法传几个一个类的实例变得非常有用。

    但需要注意的是它并不会执行init(对象实例化),即不会执行构造方法,同时也不会执行实例方法块

    • 编写一个简单的 Java 类。
    public class TestA {
    
        public static int b=2;
        private int a = 0;
    
        {
            a=2;
        }
    
        public TestA() {
            a = 1;
        }
    
        public int getA() {
            return a;
        }
    }
    

    构造方法、反射方法和 allocateInstance 方法的不同实现创建测试类

    import org.junit.Before;
    import org.junit.Test;
    import sun.misc.Unsafe;
    
    import java.lang.reflect.Field;
    
    public class UnSafeTest {
    
        static Unsafe unsafe=null;
    
        @Before
        public void init() {
            try {
                Field f = null;
                f = Unsafe.class.getDeclaredField("theUnsafe");
                f.setAccessible(true);
                unsafe = (Unsafe) f.get(null);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        @Test
        public void  createTestA() throws Exception {
            /** 使用构造器 **/
            TestA constructorA = new TestA();
            System.out.println(constructorA.getA()); //print 1
    
            /** 使用反射 **/
            TestA reflectionA = TestA.class.newInstance();
            System.out.println(reflectionA.getA()); //print 1
    
            /** 使用allocateInstance 类会被加载初始化(会执行静态代码块),但类的对象不会初始化(不会执行方法块,和构造函数)**/
            TestA unsafeA = (TestA) unsafe.allocateInstance(TestA.class);
            System.out.println(unsafeA.getA());
            System.out.println(unsafeA.b);
        }
    
    }
    

    对象的创建过程

    image

    2.2 内存修改

    Unsafe 可用于绕过安全的常用技术,直接修改内存变量。

    反射也可以实现相同的功能。但是 Unsafe 可以修改任何对象,甚至没有这些对象的引用。

    编写一个简单的 Java 类。

    public class TestA {
    
        private int ACCESS_ALLOWED = 1;
    
        public boolean giveAccess() {
            return 40 == ACCESS_ALLOWED;
        }
    }
    

    在正常情况下,giveAccess 总会返回 false。

    • 通过计算内存偏移,并使用 Unsafe.putInt() 方法,修改类种 ACCESS_ALLOWED 属性值。
    import org.junit.Before;
    import org.junit.Test;
    import sun.misc.Unsafe;
    
    import java.lang.reflect.Field;
    
    public class UnSafeTest {
    
        static Unsafe unsafe = null;
    
        @Before
        public void init() {
            try {
                Field f = null;
                f = Unsafe.class.getDeclaredField("theUnsafe");
                f.setAccessible(true);
                unsafe = (Unsafe) f.get(null);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        @Test
        public void updateFiled() throws Exception {
            /** 默认情况下打印false **/
            TestA constructorA = new TestA();
            System.out.println(constructorA.giveAccess());
            
            /**
             * 使用objectFieldOffset获取属性在内存种的偏移
             * 使用putInt修改对象内存中属性值 打印true
             * **/
            TestA unsafeA = (TestA) unsafe.allocateInstance(TestA.class);
            Field unsafeAField = unsafeA.getClass().getDeclaredField("ACCESS_ALLOWED");
            unsafe.putInt(unsafeA, unsafe.objectFieldOffset(unsafeAField), 40);
            System.out.println(unsafeA.giveAccess());
        }
    }
    
    

    2.3 动态创建类

    可以在运行时创建一个类,比如从已编译的 .class 文件中内容读取到字节数组,并正确地传递给 defineClass 方法解析加载为一个类的对象。

    编写一个简单的 Java 类。

    public class TestB {
    
        private int a = 1;
    
        public int getA() {
            return a;
        }
    
        public void setA(int a) {
            this.a = a;
        }
    }
    

    动态创建类。

    import org.junit.Before;
    import org.junit.Test;
    import org.springframework.util.FileCopyUtils;
    import sun.misc.Unsafe;
    
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.InputStream;
    import java.lang.reflect.Field;
    
    public class UnSafeTest {
    
        static Unsafe unsafe = null;
    
        @Before
        public void init() {
            try {
                Field f = null;
                f = Unsafe.class.getDeclaredField("theUnsafe");
                f.setAccessible(true);
                unsafe = (Unsafe) f.get(null);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        @Test
        public void createClass() throws Exception{
            byte[] classContents = new byte[0];
            classContents = getClassContent();
            Class c= (Class) unsafe.defineClass(null,classContents,0,classContents.length,this.getClass().getClassLoader(),null);;
            System.out.println(c.getMethod("getA").invoke(c.newInstance(), null));
        }
    
        private static byte[] getClassContent() throws Exception {
            /** 以下几种方式都是相同的 **/
            UnSafeTest.class.getResource("TestB.class");
    //        UnSafeTest.class.getResource("/unsafe/TestB.class");
    //        ClassLoader.getSystemResource("unsafe/TestB.class");
    //        ClassLoader.getSystemResource("unsafe/TestB.class");
            return FileCopyUtils.copyToByteArray(UnSafeTest.class.getResource("TestB.class").openStream());
        }
    }
    

    2.4 大数组

    Java 数组大小的最大值为 Integer.MAX_VALUE。使用直接内存分配,创建的数组大小受限于堆大小。
    Unsafe 分配的内存,分配在非堆内存,因为不执行任何边界检查,所以任何非法访问都可能会导致 JVM 崩溃。

    • 在需要分配大的连续区域、实时编程(不能容忍 JVM 延迟)时,可以使用它。java.nio 使用这一技术。

    创建一个 Java 类

    public class SuperArray {
    
        private final static int BYTE = 1;
    
        private long size;
        private long address;
    
        public SuperArray(long size) {
            this.size = size;
            address = getUnsafe().allocateMemory(size * BYTE);
        }
    
        public void set(long i, byte value) {
            getUnsafe().putByte(address + i * BYTE, value);
        }
    
        public int get(long idx) {
            return getUnsafe().getByte(address + idx * BYTE);
        }
    
        public long size() {
            return size;
        }
    
        private static Unsafe getUnsafe() {
            Field f = null;
            Unsafe unsafe = null;
            try {
                f = Unsafe.class.getDeclaredField("theUnsafe");
                f.setAccessible(true);
                unsafe = (Unsafe) f.get(null);
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            return unsafe;
        }
    }
    

    使用大数组。

        @Test
        public void createBigArray(){
    
            long SUPER_SIZE = (long) Integer.MAX_VALUE * 2;
            SuperArray array = new SuperArray(SUPER_SIZE);
            System.out.println("Array size:" + array.size()); //print 4294967294
            int sum = 0;
            for (int i = 0; i < 100; i++) {
                array.set((long) Integer.MAX_VALUE + i, (byte) 3);
                sum += array.get((long) Integer.MAX_VALUE + i);
            }
            System.out.println("Sum of 100 elements:" + sum);  //print 300
        }
    

    2.5 并发应用

    compareAndSwap 方法是原子的,并且可用来实现高性能的、无锁的数据结构。

    创建一个 Java 类。

    public class CASCounter {
    
        private volatile long counter = 0;
        private Unsafe unsafe;
        private long offset;
    
        public CASCounter() throws Exception {
            unsafe = getUnsafe();
            offset = unsafe.objectFieldOffset(CASCounter.class.getDeclaredField("counter"));
        }
    
        public void increment() {
            long before = counter;
            while (!unsafe.compareAndSwapLong(this, offset, before, before + 1)) {
                before = counter;
            }
        }
    
        public long getCounter() {
            return counter;
        }
    
        private static Unsafe getUnsafe() {
            Field f = null;
            Unsafe unsafe = null;
            try {
                f = Unsafe.class.getDeclaredField("theUnsafe");
                f.setAccessible(true);
                unsafe = (Unsafe) f.get(null);
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            return unsafe;
        }
    }
    

    使用无锁的数据结构。

     @Test
        public void cas() {
            final TestB b = new TestB();
            Thread threadA = new Thread(new Runnable() {
                @Override
                public void run() {
                    b.counter.increment();
                }
            });
            Thread threadB = new Thread(new Runnable() {
                @Override
                public void run() {
                    b.counter.increment();
                }
            });
            Thread threadC = new Thread(new Runnable() {
                @Override
                public void run() {
                    b.counter.increment();
                }
            });
            threadA.start();
            threadB.start();
            threadC.start();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(b.counter.getCounter()); //print 3
        }
    
    
        private static class TestB {
            private CASCounter counter;
    
            public TestB() {
                try {
                    counter = new CASCounter();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    

    2.6 挂起与恢复

    //传入的线程将归还许可
    public native void unpark(Thread jthread);  
    
    //当前线程获取许可,成功则返回,失败则阻塞,并提供了超时机制。
    //阻塞响应中断
    //isAbsolute 参数是指明时间是否是绝对时间
    //isAbsolute=true时  时间是绝对时间,时间单位为ms
    //isAbsolute=flase时 时间是相对时间,时间单位为ns
    public native void park(boolean isAbsolute, long time); // 
    

    park :表示获取许可,默认情况下线程中不存在许可。当前线程第一次使用Unsafe 调用park方法时(如果未调用unpark会导致当前线程阻塞。

    其中参数isAbsolute用来表示是否为绝对时间。

    • 当isAbsolute为false
      • time等于0,如果没有获取许可线程会一直阻塞
      • time大于0,这里的单位是ns(纳秒),如果线程阻塞,time ns(纳秒)后会从阻塞中唤醒返回
        @Test
        public void park1(){
            unsafe.park(false, 0);
        }
        
        @Test
        public void park2(){
            long star=System.currentTimeMillis();
            unsafe.park(false, 5000000000L);
            System.out.println(System.currentTimeMillis()-star);
        }
    
    • 当isAbsolute是true,
      • time大于0,这里的单位是ms(毫秒),如果线程阻塞,time ms(毫秒)后会从阻塞中唤醒返回
      • time 小于等于0,则直接返回
        @Test
        public void park3(){
            long star=System.currentTimeMillis();
            unsafe.park(true, System.currentTimeMillis()+5000L);
            System.out.println(System.currentTimeMillis()-star);
        }
        
        @Test
        public void park4(){
            unsafe.park(true, 0L);
        }
    

    unpark:表示归还许可,同一个线程多次归还许可只相同一次。(许可仅有一个)

    @Test
        public void park2() {
            Thread currThread = Thread.currentThread();
            unsafe.unpark(currThread);
            unsafe.unpark(currThread);
            unsafe.unpark(currThread);
    
            unsafe.park(false, 0);
            unsafe.park(false, 0);
            System.out.println("execute success"); // 线程挂起,不会打印。
        }
    

    整个并发框架中对线程的挂起操作被封装在 LockSupport 类中,LockSupport 类中有各种版本 pack 方法,但最终都调用的 Unsafe.park() 方法

    unpark 无法恢复处于 sleep 中的线程,只能与 park 配对使用,因为 unpark 发放的许可只有 park 能监听到。

    相关文章

      网友评论

          本文标题:J.U.C之Unsafe: Unsafe 创建和运用场景

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