java基础之Unsafe

本文深入探讨Java的Unsafe API,揭示其内部机制,包括如何获取Unsafe实例,及其实现C风格的sizeof函数、创建大数组、多继承和动态类定义的方法。同时,文章提供了详细的代码示例,帮助理解Unsafe的各种高级用法。
摘要由CSDN通过智能技术生成

简介

  1. Unsafe类具有私有构造函数,并且是单例的。虽然Unsafe具有静态的getUnsafe()方法,但是直接调用Unsafe.getUnsafe(),则可能会收到SecurityException,此方法仅可被用于用于受信任的代码。如果代码是受信任的,会判断是不是系统类加载器

    @CallerSensitive
    public static Unsafe getUnsafe() {
        Class<?> caller = Reflection.getCallerClass();
        if (!VM.isSystemDomainLoader(caller.getClassLoader()))
            throw new SecurityException("Unsafe");
        return theUnsafe;
    }
    
  2. 代码变为受信任的的方式

    1. 增加JVM虚拟机选项
    java -Xbootclasspath:/usr/jdk1.7.0/jre/lib/rt.jar:  ${path}   // 其中path为调用Unsafe相关方法的类所在jar包路径 
        
    
  3. 通过反射获取Unsafe

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

方法

  1. Unsafe大部分方法都是native方法,分为几类

  2. Info:主要返回系统相关信息的方法

    //返回系统指针的大小。返回值为4(32位系统)或 8(64位系统)。
    public native int addressSize();  
    //内存页的大小,此值为2的幂次方。
    public native int pageSize();
    
    //8
    System.out.println(unsafe.addressSize());
    //4096
    System.out.println(unsafe.pageSize());
    
  3. Objects:提供对象及其字段操作的方法

    //返回对象成员属性在内存地址相对于此对象的内存地址的偏移量
    public native long objectFieldOffset(Field f);
    //获得给定对象的指定地址偏移量的值,与此类似操作还有:getInt,getDouble,getLong,getChar等
    public native Object getObject(Object o, long offset);
    //给定对象的指定地址偏移量设值,与此类似操作还有:putInt,putDouble,putLong,putChar等
    public native void putObject(Object o, long offset, Object x);
    //从对象的指定偏移量处获取变量的引用,使用volatile的加载语义
    public native Object getObjectVolatile(Object o, long offset);
    //存储变量的引用到对象的指定的偏移量处,使用volatile的存储语义
    public native void putObjectVolatile(Object o, long offset, Object x);
    //有序、延迟版本的putObjectVolatile方法,不保证值的改变被其他线程立即看到。只有在field被volatile修饰符修饰时有效
    public native void putOrderedObject(Object o, long offset, Object x);
    //绕过构造方法、初始化代码来创建对象
    public native Object allocateInstance(Class<?> cls) throws InstantiationException;
    
    
    
    1. 创建User类
    class User {
        private int age;
        private String name;
        ...省略...
    }  
    
    2. 实例化User
    
    User user = (User) unsafe.allocateInstance(User.class);
    user.setAge(18);
    user.setName("jannal");
    /**
     * age:对应的内存偏移地址12
     * name:对应的内存偏移地址16
     */
    for (Field field : User.class.getDeclaredFields()) {
        System.out.println(field.getName() + ":对应的内存偏移地址" + unsafe.objectFieldOffset(field));
    }
    
  4. Classes:提供用于类和静态字段操作的方法

    //获取给定静态字段的内存地址偏移量,这个值对于给定的字段是唯一且固定不变的
    public native long staticFieldOffset(Field f);
    //获取一个静态类中给定字段的对象指针
    public native Object staticFieldBase(Field f);
    //判断是否需要初始化一个类,通常在获取一个类的静态属性的时候(因为一个类如果没初始化,它的静态属性也不会初始化)使用。 当且仅当ensureClassInitialized方法不生效时返回false。
    public native boolean shouldBeInitialized(Class<?> c);
    //检测给定的类是否已经初始化。通常在获取一个类的静态属性的时候(因为一个类如果没初始化,它的静态属性也不会初始化)使用。
    public native void ensureClassInitialized(Class<?> c);
    //定义一个类,此方法会跳过JVM的所有安全检查,默认情况下,ClassLoader(类加载器)和ProtectionDomain(保护域)实例来源于调用者
    public native Class<?> defineClass(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain);
    //定义一个匿名类
    public native Class<?> defineAnonymousClass(Class<?> hostClass, byte[] data, Object[] cpPatches);
    
  5. Array:数组操作方法

    //返回数组中第一个元素的偏移地址
    public native int arrayBaseOffset(Class<?> arrayClass);
    //返回数组中一个元素占用的大小
    public native int arrayIndexScale(Class<?> arrayClass);
    
  6. Synchronization:主要提供低级别同步原语

    //取消阻塞线程
    public native void unpark(Object thread);
    //阻塞线程
    public native void park(boolean isAbsolute, long time);
    //获得对象锁(可重入锁)
    @Deprecated
    public native void monitorEnter(Object o);
    //释放对象锁
    @Deprecated
    public native void monitorExit(Object o);
    //尝试获取对象锁
    @Deprecated
    public native boolean tryMonitorEnter(Object o);
    
    
    /*** 
     * @param obj 包含要修改field的对象 
     * @param offset obj中整型field的偏移量 
     * @param expect 期望field中存在的值 
     * @param update 如果期望值expect与field的当前值相同,设置filed的值为这个新值 
     * @return true 如果field的值被更改返回true
     */    
    public final native boolean compareAndSwapInt(Object o, long offset,
                                                      int expected,
                                                      int x);  
    public final native boolean compareAndSwapObject(Object o, long offset,  Object expected, Object update);
    
    public final native boolean compareAndSwapLong(Object o, long offset, long expected, long update);
    
  7. Memory:直接内存访问方法,绕过JVM堆直接操纵本地内存

    //分配内存, 相当于C++的malloc函数
    public native long allocateMemory(long bytes)
    //扩充内存
    public native long reallocateMemory(long address, 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)    
    //获取给定地址值,忽略修饰限定符的访问限制。与此类似操作还有: getInt,getDouble,getLong,getChar等
    public native Object getObject(Object o, long offset);
    //为给定地址设置值,忽略修饰限定符的访问限制,与此类似操作还有: putInt,putDouble,putLong,putChar等
    public native void putObject(Object o, long offset, Object x);
    //获取给定地址的byte类型的值(当且仅当该内存地址为allocateMemory分配时,此方法结果为确定的)
    public native byte getByte(long address);
    //为给定地址设置byte类型的值(当且仅当该内存地址为allocateMemory分配时,此方法结果才是确定的)
    public native void putByte(long address, byte x);  
    

方法详解

allocateInstance

  1. 如果要跳过对象初始化或者绕过构造函数的安全检查或者类没有public的构造函数时,可以使用allocateInstance

    public class Person {
        private long age;
    
        public Person() {
            this.age = 10;
        }
    
        public long age() {
            return age;
        }
    }
    
    
    @Test
    public void testAllocateInstance() throws Exception {
        Unsafe unsafe = unsafe();
        Person person = new Person();
        //输出10
        System.out.println(person.age());
    
        person = Person.class.newInstance();
        //输出10
        System.out.println(person.age());
    
        person = (Person) unsafe.allocateInstance(Person.class);
        //输出0
        System.out.println(person.age());
    }
    

抛出异常

  1. 如果不喜欢Checked Exception,可以使用如下方式,此方法抛出检查异常,但是代码没有被强制捕获或重新抛出它,就像运行时异常一样

    getUnsafe().throwException(new IOException());
    

SizeOf

  1. 使用objectFieldOffset()方法,我们可以实现C样式的sizeof函数。此实现返回对象的浅层大小( shallow size of object)

    /**
     * 实际上,为了获得良好,安全和准确的sizeof函数,最好使用java.lang.instrument包,
     * 但是它需要在JVM中指定agent选项。
     */
    public static long sizeOf(Object o) {
        Unsafe unsafe = null;
        try {
            unsafe = unsafe();
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage(), e);
        }
        HashSet<Field> fields = new HashSet<Field>();
        Class c = o.getClass();
        //遍历当前类以及超类所有非静态字段
        while (c != Object.class) {
            for (Field f : c.getDeclaredFields()) {
                if ((f.getModifiers() & Modifier.STATIC) == 0) {
                    fields.add(f);
                }
            }
            c = c.getSuperclass();
        }
        // get offset
        long maxSize = 0;
        for (Field f : fields) {
            long offset = unsafe.objectFieldOffset(f);
            if (offset > maxSize) {
                maxSize = offset;
            }
        }
        return ((maxSize / 8) + 1) * 8;   // padding
    }
    

大数组

  1. java数组的最大大小是Integer.MAX_VALUE。使用直接内存分配,我们可以创建大小仅受堆大小限制的数组。

    //java.lang.OutOfMemoryError: Requested array size exceeds VM limit
    int[] a = new int[Integer.MAX_VALUE];
    //java.lang.OutOfMemoryError: Java heap space
    int[] b = new int[Integer.MAX_VALUE - 8]; 
    数组需要8个字节来存储大小,所以数组最大长度是Integer.MAX_VALUE - 8
    
  2. 实现大数组

    public class SuperArray {
        public static final Unsafe UNSAFE = unsafe();
        private final static int BYTE = 1;
        private long size;
        private long address;
    
        public SuperArray(long size) {
            this.size = size;
            //以这种方式分配的内存不在堆中,也不在GC的管理之下,因此需要使用Unsafe.freeMemory()进行处理。
            //它还不执行任何边界检查,因此任何非法访问都可能导致JVM崩溃。
            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;
        }
    
        private static Unsafe unsafe() {
            try {
                Field singleoneInstanceField = Unsafe.class.getDeclaredField("theUnsafe");
                singleoneInstanceField.setAccessible(true);
                return (Unsafe) singleoneInstanceField.get(null);
            } catch (Exception e) {
                throw new RuntimeException(e.getMessage(), e);
            }
        }
    
        public void free() {
            UNSAFE.freeMemory(address);
        }
    }
    
    public class SuperArrayTest {
        @Test
        public void testSuperArray() {
            long SUPER_SIZE = (long) Integer.MAX_VALUE * 2;
            SuperArray array = new SuperArray(SUPER_SIZE);
            // 4294967294
            System.out.println("Array size:" + array.size());
            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);
            }
            // 300
            System.out.println("100个元素的和:" + sum);
            //释放内存
            array.free();
        }
    }
    
    

多继承

  1. 在Java中本来是没有多继承的。除非我们可以将任意的类型转为我们想要的任意类型。除非我们可以转换任意类型到任意其他类型。

    long intClassAddress = normalize(getUnsafe().getInt(new Integer(0),4L));
    long strClassAddress = normalize(getUnsafe().getInt("",4L));
    getUnsafe().putAddress(intClassAddress+36,strClassAddress);
    

    这段代码添加 String 类成为 Integer 的父类,所以,我们可以直接转换,而不会有运行时异常。

    (String)(Object)(new Integer(666))
    

动态class

  1. 我们可以在运行时创建类,例如读取编译好的 .class 文件。然后执行读 class 内容到 byte 数组,并且传递给 defineClass 方法。

    byte[] classContents = getClassContent();
    Class c = getUnsafe().defineClass(null,classContents,0,classContents.length);
    c.getMethod("a").invoke(c.newInstance(),null);// 1
    
    private static byte[] getClassContent() throws Exception{
      File f = new File("/home/mishadoff/tmp/A.class");
      FileInputStream input = new FileInputStream(f);
      byte[] content = new byte[(int)f.length()];
      input.read(content);
      input.close();
      return content;
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值