关于Unsafe

228 阅读4分钟

简介

顾名思义他是不安全的,他的功能如下图

QQ截图20220621221736.png

@CallerSensitive
public static Unsafe getUnsafe() {
    //利用这个可以获取调用者的类
    Class var0 = Reflection.getCallerClass();
    //这句话判断类的加载器是不是主类加载器,也就是ClassLoader为null
    if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
        throw new SecurityException("Unsafe");
    } else {
        return theUnsafe;
    }
}

数组相关

arrayBaseOffset方法返回数组在内存中的偏移量,这个值是固定的。arrayIndexScale返回数组中的每一个元素的内存地址换算因子。举个栗子,double数组(注意不是包装类型)每个元素占用8个字节,所以换算因子为8,int类型则为4,通过这两个方法我们就能定位数组中每个元素的内存地址,从而赋值,下面代码演示:


public class arrayDemo {
    public static void main(String[] args) throws Exception {
        // 用反射 new 对象
        Class<Unsafe> unsafeClass = Unsafe.class;
        Constructor<Unsafe> constructor = unsafeClass.getDeclaredConstructor();
        constructor.setAccessible(true);
        Unsafe unsafe = constructor.newInstance();

        Integer[] integers = new Integer[10];
        // 打印数组的原始值
        System.out.println(Arrays.toString(integers));
        // [null, null, null, null, null, null, null, null, null, null]
        // 获取Integer数组在内存中的固定的偏移量
        long arrayBaseOffset = unsafe.arrayBaseOffset(Integer[].class);
        System.out.println(unsafe.arrayIndexScale(Integer[].class));
        // 4
        System.out.println(unsafe.arrayIndexScale(double[].class));
        // 8
        // 将数组中第一个元素的更新为100
        unsafe.putObject(integers, arrayBaseOffset, 100);
        // 将数组中第五个元素更新为50  注意 引用类型占用4个字节,所以内存地址 需要 4 * 4 = 16
        unsafe.putObject(integers, arrayBaseOffset + 16, 50);
        // 打印更新后的值
        System.out.println(Arrays.toString(integers));
        // [100, null, null, null, 50, null, null, null, null, null]
    }
}

输出

[null, null, null, null, null, null, null, null, null, null]
4
8
[100, null, null, null, 50, null, null, null, null, null]

内存屏障

//分配内存, 相当于C++的malloc函数
public native long allocateMemory ( 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);
//为给定地址设置值,忽略修饰限定符
public class demo1 {
    public static void main(String[] args) throws Exception {
        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        Unsafe unsafe = (Unsafe) theUnsafe.get(null);

        // 分配 10M的堆外内存
        long _10M_Address = unsafe.allocateMemory(1 * 1024 * 1024 * 10);
        // 将10M内存的 前面1M内存值设置为10
        unsafe.setMemory(_10M_Address, 1 * 1024 * 1024 * 1, (byte) 10);
        // 获取第1M内存的值: 10
        System.out.println(unsafe.getByte(_10M_Address + 1000));
        // 获取第1M内存后的值: 0(没有设置)
        System.out.println(unsafe.getByte(_10M_Address + 1 * 1024 * 1023 * 1));

        System.out.println(unsafe.getByte(_10M_Address + 100 * 1 * 5));

//        System.out.println(unsafe.getByte(_10M_Address + 100));
    }
}

系统相关

线程调度

public class threadDemo {
    public static void main(String[] args) throws Exception {
        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        Unsafe unsafe = (Unsafe) theUnsafe.get(null);
        /**
         * 线程许可
         * 许可线程通过(park),或者让线程等待许可(unpark),
         */
        Thread packThread = new Thread(() -> {
            long startTime = System.currentTimeMillis();
            //纳秒,相对时间park
            unsafe.park(false,3000000000L);
            System.out.println("我三秒等完了");
            //毫秒,绝对时间park
            //unsafe.park(true,System.currentTimeMillis()+3000);

            System.out.println("main thread end,cost :"+(System.currentTimeMillis()-startTime)+"ms");
        });
        System.out.println("hello");
        packThread.start();
        TimeUnit.SECONDS.sleep(1);
        System.out.println("我说完了");
    }
}

运行结果

hello
我说完了
我三秒等完了
main thread end,cost :3008ms
public class threadDemo {
    public static void main(String[] args) throws Exception {
        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        Unsafe unsafe = (Unsafe) theUnsafe.get(null);

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                if (i == 5) {
                    // i == 5时,将当前线程挂起
                    unsafe.park(false, 0L);
                }
                System.out.println(Thread.currentThread().getName() + " printing i : " + i);
            }
        }, " Thread__Unsafe__1");

        t1.start();

        // 主线程休息三秒
        Thread.sleep(3000L);
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + " printing i : " + i);
            if (i == 9) {
                // 将线程 t1 唤醒
                unsafe.unpark(t1);
            }
        }

        System.in.read();
    }
}
 Thread__Unsafe__1 printing i : 0
 Thread__Unsafe__1 printing i : 1
 Thread__Unsafe__1 printing i : 2
 Thread__Unsafe__1 printing i : 3
 Thread__Unsafe__1 printing i : 4
main printing i : 0
main printing i : 1
main printing i : 2
main printing i : 3
main printing i : 4
main printing i : 5
main printing i : 6
main printing i : 7
main printing i : 8
main printing i : 9
 Thread__Unsafe__1 printing i : 5
 Thread__Unsafe__1 printing i : 6
 Thread__Unsafe__1 printing i : 7
 Thread__Unsafe__1 printing i : 8
 Thread__Unsafe__1 printing i : 9

内存操作

CAS

Class

对象操作

public class OperateObjectExample {

    /**
     *  1、public native Object allocateInstance(Class<?> var1); 分配内存
     *  2、public native Class<?> defineClass(String var1, byte[] var2, int var3, int var4, ClassLoader var5, ProtectionDomain var6); 方法定义一个类用于动态的创建类
     * @throws Exception
     */
    public static void operateObjectUseUnsafe() throws Exception{

        Unsafe unsafe = UnsafeFactory.getUnsafe();

        // 使用Unsafe的allocateInstance()方法,可以无需使用构造函数的情况下实例化对象
        User user = (User) unsafe.allocateInstance(User.class);
        user.setId(1);
        user.setName("李子捌");
        System.out.println(user);

        // 返回对象成员属性在内存中相对于对象在内存中地址的偏移量
        Field name = User.class.getDeclaredField("name");
        long fieldOffset = unsafe.objectFieldOffset(name);

        // 使用Unsafe的putXxx()方法,可以直接修改内存地址指向的数据(可以越过权限访问控制符)
        unsafe.putObject(user, fieldOffset, "李子柒");
        System.out.println(user);

        // 使用Unsafe在运行时通过.class文件,创建类
        File classFile = new File("E:\workspaceall\liziba-javap5\out\production\liziba-javap5\com\liziba\unsafe\pojo\User.class");
        FileInputStream fis = new FileInputStream(classFile);
        byte [] classContent = new byte[(int) classFile.length()];
        fis.read(classContent);
        Class<?> clazz = unsafe.defineClass(null, classContent, 0, classContent.length, null, null);
        Constructor<?> constructor = clazz.getDeclaredConstructor(int.class, String.class);
        System.out.println(constructor.newInstance(1, "李子玖"));

    }

    public static void main(String[] args) {
        try {
            OperateObjectExample.operateObjectUseUnsafe();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

图片.png

应用

创建对象

/**
 * Java数组大小的最大值为Integer.MAX_VALUE。使用直接内存分配,我们创建的数组大小受限于堆大小;
 * 实际上,这是堆外内存(off-heap memory)技术,在java.nio包中部分可用;
 *
 * 这种方式的内存分配不在堆上,且不受GC管理,所以必须小心Unsafe.freeMemory()的使用。
 * 它也不执行任何边界检查,所以任何非法访问可能会导致JVM崩溃
 */
public class SuperArray {
 
    private static Unsafe unsafe = null;
    private static Field getUnsafe = null;
 
    static {
        try {
            getUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
            getUnsafe.setAccessible(true);
            unsafe = (Unsafe) getUnsafe.get(null);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
 
    private final static int BYTE = 1;
 
    private long size;
    
    private long address;
 
    public SuperArray(long size) {
        this.size = size;
        address = unsafe.allocateMemory(size * BYTE);
    }
 
    public void set(long i, byte value) {
        unsafe.putByte(address + i * BYTE, value);
    }
 
    public int get(long idx) {
        return unsafe.getByte(address + idx * BYTE);
    }

    public long size() {
        return size;
    }
 
    public static void main(String[] args) {
        long SUPER_SIZE = (long)Integer.MAX_VALUE * 2;
        SuperArray array = new SuperArray(SUPER_SIZE);
        System.out.println("Array size:" + array.size()); // 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);
    } 
}

谢谢观看,关于Usafe待补全

www.zhihu.com/question/29…

www.jianshu.com/p/9aa2aa6ff…

blog.csdn.net/u010398771/…

有深浅拷贝案例: Java之Unsafe-越迷人的越危险 - 掘金 (juejin.cn)

Java程序员必须要了解的类Unsafe - 云+社区 - 腾讯云 (tencent.com)