美文网首页Java源码
【Java源码计划】Unsafe<rt.jar_sun.m

【Java源码计划】Unsafe<rt.jar_sun.m

作者: DeanChangDM | 来源:发表于2019-02-27 15:11 被阅读21次

    Unsafe

    就像这个类名一样,这个类本身就不是很安全,Unsafe类使Java拥有了像C语言的指针一样操作内存空间的能力,同时也带来了指针的问题。过度的使用Unsafe类会使得出错的几率变大,因此Java官方并不建议使用的,官方文档也几乎没有。所有使用这类的都是依赖着一些stackoverflow上面或者查出来的一些不完善的资料来学习。因此我的翻译和解读也只能按照自己的理解来,希望大家一起探讨。

    Orcale的JDK8无法获取到这个源码,可以再OpenJDK中找到,或者通过IDEA等工具的反编译功能看到

    Unsafe并不在SE的范畴内,但是很多基础类库却又都是基于Unsafe开发的,比如Netty、Cassandra、Hadoop、Kafka等,还有很多JUC都是用到了Unsafe,Java9 oracle要取消,由于我现在这个文档是按照8来写的,说实话9我也没看,所以不清楚目前是什么情况,可能使用的时候需要按照Jigsaw一样的去操作模块,关于Java9中Unsafe的处理可以参考一下下面的内容
    1.What to do about sun.misc.Unsafe and Pals?文档
    2.stackoverflow上的一个问题
    3.Oracle官方解决方案
    4.为什么JUC中大量使用了sun.misc.Unsafe 这个类,但官方却不建议开发者使用

    说回来,感觉上这Unsafe就好像是Java的一个后门,这个类下面就是在访问Native方法了,提供了一些绕开JVM的更底层功能,由此提高效率,使用的时候一定要注意,没有深入了解轻易不要使用

    1. 就像上面说的Unsafe有可能在未来的Jdk版本移除或者不允许Java应用代码使用,这一点可能导致使用了Unsafe的应用无法运行在高版本的Jdk
    2. Unsafe的不少方法中必须提供原始地址(内存地址)和被替换对象的地址,偏移量要自己计算,一旦出现问题就是JVM崩溃级别的异常,会导致整个JVM实例崩溃,表现为应用程序直接crash掉
    3. Unsafe提供的直接内存访问的方法中使用的内存不受JVM管理(无法被GC),需要手动管理,一旦出现疏忽很有可能成为内存泄漏的源头。

    Unsafe使用了单例模式,要用通过一个静态方法getUnsafe()来获取。但Unsafe类做了限制,如果是普通的调用的话,它会抛出一个SecurityException异常;只有由主类加载器加载的类才能调用这个方法,

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

    方法上面还有个注解,CallerSensitive,说起这个注解,其实是为了堵住漏洞用的。曾经有黑客通过构造双重反射来提升权限,原理是当时反射只检查固定深度的调用者的类,看它有没有特权,例如固定看两层的调用者(getCallerClass(2))。如果我的类本来没足够权限群访问某些信息,那我就可以通过双重反射去达到目的:反射相关的类是有很高权限的,而在 我->反射1->反射2 这样的调用链上,反射2检查权限时看到的是反射1的类,这就被欺骗了,导致安全漏洞。使用CallerSensitive后,getCallerClass不再用固定深度去寻找actual caller(“我”),而是把所有跟反射相关的接口方法都标注上CallerSensitive,搜索时凡看到该注解都直接跳过,这样就有效解决了前面举例的问题

    由于这个对于classloader的限制,想要在我们的一般代码中使用,需在使用的时候还需要用点取巧的方式,比较常用的是利用反射来做

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

    当然也可以用像设置bootclasspath参数的方式来让用户代码被主类加载器加载。

    Unsafe还提供了很多内容,像是

    1. 内存管理,用于分配内存、释放内存等
    2. 对非常规的对象实例化
    3. 操作类、对象、变量
    4. 数组操作
    5. 多线程同步
    6. 线程挂起与恢复
    7. 内存屏障
    8. 等等

    源码解析

    这个类中的大多数方法都是非常底层的,并且对应于少量的硬件指令,鼓励编译器优化相应的方法

    首先是一些常量,以及对应的初始化代码

    //Unsafe实例(单例模式)
    private static final Unsafe theUnsafe;
    //非法的字段偏移量
    public static final int INVALID_FIELD_OFFSET = -1;
    //Boolean型数组偏移
    public static final int ARRAY_BOOLEAN_BASE_OFFSET;
    //Byte型数组偏移量
    public static final int ARRAY_BYTE_BASE_OFFSET;
    //short型数组偏移量
    public static final int ARRAY_SHORT_BASE_OFFSET;
    //char型数组偏移量
    public static final int ARRAY_CHAR_BASE_OFFSET;
    //int型数组偏移量
    public static final int ARRAY_INT_BASE_OFFSET;
    //long型数组偏移量
    public static final int ARRAY_LONG_BASE_OFFSET;
    //float型数组偏移量
    public static final int ARRAY_FLOAT_BASE_OFFSET;
    //double型数组偏移量
    public static final int ARRAY_DOUBLE_BASE_OFFSET;
    //Object型数组偏移量
    public static final int ARRAY_OBJECT_BASE_OFFSET;
    
    //boolean数组一个元素占用的字节数
    public static final int ARRAY_BOOLEAN_INDEX_SCALE;
    //byte数组一个元素占用的字节数
    public static final int ARRAY_BYTE_INDEX_SCALE;
    //short数组一个元素占用的字节数
    public static final int ARRAY_SHORT_INDEX_SCALE;
    //char数组一个元素占用的字节数
    public static final int ARRAY_CHAR_INDEX_SCALE;
    //int数组一个元素占用的字节数
    public static final int ARRAY_INT_INDEX_SCALE;
    //long数组一个元素占用的字节数
    public static final int ARRAY_LONG_INDEX_SCALE;
    //float数组一个元素占用的字节数
    public static final int ARRAY_FLOAT_INDEX_SCALE;
    //double数组一个元素占用的字节数
    public static final int ARRAY_DOUBLE_INDEX_SCALE;
    //Object数组一个元素占用的字节数
    public static final int ARRAY_OBJECT_INDEX_SCALE;
    //初始化了JVM的地址大小,如果是32位JVM这个值就是4,如果是64位的JVM,这个值就是8
    public static final int ADDRESS_SIZE;
    
    
    static {
            //调用注册本地方法,以便JVM可以找到对应的本地方法
            registerNatives();
            //注册方法到反射过滤器,使得这个方法不能被反射得到,虽然并没有什么用,
            //我们都是直接反射字段的
            Reflection.registerMethodsToFilter(Unsafe.class, new String[]{"getUnsafe"});
            //初始化Unsafe
            theUnsafe = new Unsafe();
            
            //初始化对应的常量
            ARRAY_BOOLEAN_BASE_OFFSET = theUnsafe.arrayBaseOffset(boolean[].class);
            ARRAY_BYTE_BASE_OFFSET = theUnsafe.arrayBaseOffset(byte[].class);
            ARRAY_SHORT_BASE_OFFSET = theUnsafe.arrayBaseOffset(short[].class);
            ARRAY_CHAR_BASE_OFFSET = theUnsafe.arrayBaseOffset(char[].class);
            ARRAY_INT_BASE_OFFSET = theUnsafe.arrayBaseOffset(int[].class);
            ARRAY_LONG_BASE_OFFSET = theUnsafe.arrayBaseOffset(long[].class);
            ARRAY_FLOAT_BASE_OFFSET = theUnsafe.arrayBaseOffset(float[].class);
            ARRAY_DOUBLE_BASE_OFFSET = theUnsafe.arrayBaseOffset(double[].class);
            ARRAY_OBJECT_BASE_OFFSET = theUnsafe.arrayBaseOffset(Object[].class);
            ARRAY_BOOLEAN_INDEX_SCALE = theUnsafe.arrayIndexScale(boolean[].class);
            ARRAY_BYTE_INDEX_SCALE = theUnsafe.arrayIndexScale(byte[].class);
            ARRAY_SHORT_INDEX_SCALE = theUnsafe.arrayIndexScale(short[].class);
            ARRAY_CHAR_INDEX_SCALE = theUnsafe.arrayIndexScale(char[].class);
            ARRAY_INT_INDEX_SCALE = theUnsafe.arrayIndexScale(int[].class);
            ARRAY_LONG_INDEX_SCALE = theUnsafe.arrayIndexScale(long[].class);
            ARRAY_FLOAT_INDEX_SCALE = theUnsafe.arrayIndexScale(float[].class);
            ARRAY_DOUBLE_INDEX_SCALE = theUnsafe.arrayIndexScale(double[].class);
            ARRAY_OBJECT_INDEX_SCALE = theUnsafe.arrayIndexScale(Object[].class);
            ADDRESS_SIZE = theUnsafe.addressSize();
        }
    

    接下来是一些读写操作,编译器会把这些优化为内存操作,这些方法可以作用在位于Java堆中的对象字段,不能作用于封装数组的元素

    读操作

    通过给定的参数读取值
    更具体地说是通过给定的对象和给定的偏移来读取一个字段或者是一个数组中元素的值,或者对象为null时读出一个偏移量跟这个给定的偏移量相同的数值元素(详见下文)

    只有最少满足以下情况中的一个时出现时结果才不为undefined

    • 偏移量是通过给定的一个Java字段的Field对象用下面的objectFieldOffset计算出来的,同时给定的对象也是和之前的Field获取时的对象相匹配
    • 偏移量和给定的对象都是通过给定一个通过反射获取的某个Java 字段的Field对象作为参数,通过下面的staticFieldOffset方法和staticFieldBase方法获取到的。
    • 对象所引用的是一个数组,偏移量是通过公式B+N * S 计算出来的一个整数,其中N是数组中的一个合法的索引,B和S是通过调用下面的arrayBaseOffset和arrayIndexScale方法利用数组的类获取出来的。返回的元素就是数组第N个值

    值得注意的是,即便满足了上面的某一个要求,调用也引用到了这个特定的Java变量,但是如果这个变量类型和方法的返回类型不匹配,此时也会是undefined

    该方法通过两个参数引用一个变量,这就意味着实际上它为Java对象提供了双寄存器寻址方式。当Object为null的时候,这个方法会将偏移量作为绝对地址。这种方式使用的时候类似于getInt(long)这个单参数的方法,单参数的方法为Java基本类型提供了单寄存器寻址方式。但是在内存中这两种变量有不同的布局,不能假设这两种寻址模式是等效的。此外还应该记住,双寄存器寻址模式的偏移量不能与单寄存器寻址模式中使用的long混淆。

    参数Object 是变量驻留的Java堆内的对象,如果没有可以为空
    参数long 是变量在Java堆对象中驻留的位置(或者偏移量),如果为对象为null,就是变量抵制的静态内存地址。
    返回对应类型的从所给的指示地址中取出的值
    可能会抛出RuntimeException

    写操作

    将给定的值写入给定的变量
    前两个参数作用跟getInt方法作用一致,用于引用特定的变量字段或者数组元素
    最后一个变量则是要写入的值。写入值和目标变量必须类型一致

    其他方法类似,需要特殊说明的以注释的方式写在下面的方法上

        public native int getInt(Object var1, long var2);
    
        public native void putInt(Object var1, long var2, int var4);
    
        public native Object getObject(Object var1, long var2);
        
        //给出的第三个参数,也就是要写入的变量,要么是null
        //要么必须和目标字段的类型匹配,否则结果可能是出错
        //如果第一个参数也就是对象不为null,
        //那么内存标志或者其他的内存屏障在虚拟机需要的时候会被更新
        //关于内存这部分详见其他的有关内存的文章
        public native void putObject(Object var1, long var2, Object var4);
    
        public native boolean getBoolean(Object var1, long var2);
    
        public native void putBoolean(Object var1, long var2, boolean var4);
    
        public native byte getByte(Object var1, long var2);
    
        public native void putByte(Object var1, long var2, byte var4);
    
        public native short getShort(Object var1, long var2);
    
        public native void putShort(Object var1, long var2, short var4);
    
        public native char getChar(Object var1, long var2);
    
        public native void putChar(Object var1, long var2, char var4);
    
        public native long getLong(Object var1, long var2);
    
        public native void putLong(Object var1, long var2, long var4);
    
        public native float getFloat(Object var1, long var2);
    
        public native void putFloat(Object var1, long var2, float var4);
    
        public native double getDouble(Object var1, long var2);
    
        public native void putDouble(Object var1, long var2, double var4);
    

    下面这些方法在以前也是Native的方法,使用32bit的偏移量,现在全部改成了对上面方法的封装,将偏移量强制转换为long型。能够提供对1.4编译出来的字节码的向后兼容性

    
     /** @deprecated */
        @Deprecated
        public int getInt(Object var1, int var2) {
            return this.getInt(var1, (long)var2);
        }
    
        /** @deprecated */
        @Deprecated
        public void putInt(Object var1, int var2, int var3) {
            this.putInt(var1, (long)var2, var3);
        }
    
        /** @deprecated */
        @Deprecated
        public Object getObject(Object var1, int var2) {
            return this.getObject(var1, (long)var2);
        }
    
        /** @deprecated */
        @Deprecated
        public void putObject(Object var1, int var2, Object var3) {
            this.putObject(var1, (long)var2, var3);
        }
    
        /** @deprecated */
        @Deprecated
        public boolean getBoolean(Object var1, int var2) {
            return this.getBoolean(var1, (long)var2);
        }
    
        /** @deprecated */
        @Deprecated
        public void putBoolean(Object var1, int var2, boolean var3) {
            this.putBoolean(var1, (long)var2, var3);
        }
    
        /** @deprecated */
        @Deprecated
        public byte getByte(Object var1, int var2) {
            return this.getByte(var1, (long)var2);
        }
    
        /** @deprecated */
        @Deprecated
        public void putByte(Object var1, int var2, byte var3) {
            this.putByte(var1, (long)var2, var3);
        }
    
        /** @deprecated */
        @Deprecated
        public short getShort(Object var1, int var2) {
            return this.getShort(var1, (long)var2);
        }
    
        /** @deprecated */
        @Deprecated
        public void putShort(Object var1, int var2, short var3) {
            this.putShort(var1, (long)var2, var3);
        }
    
        /** @deprecated */
        @Deprecated
        public char getChar(Object var1, int var2) {
            return this.getChar(var1, (long)var2);
        }
    
        /** @deprecated */
        @Deprecated
        public void putChar(Object var1, int var2, char var3) {
            this.putChar(var1, (long)var2, var3);
        }
    
        /** @deprecated */
        @Deprecated
        public long getLong(Object var1, int var2) {
            return this.getLong(var1, (long)var2);
        }
    
        /** @deprecated */
        @Deprecated
        public void putLong(Object var1, int var2, long var3) {
            this.putLong(var1, (long)var2, var3);
        }
    
        /** @deprecated */
        @Deprecated
        public float getFloat(Object var1, int var2) {
            return this.getFloat(var1, (long)var2);
        }
    
        /** @deprecated */
        @Deprecated
        public void putFloat(Object var1, int var2, float var3) {
            this.putFloat(var1, (long)var2, var3);
        }
    
        /** @deprecated */
        @Deprecated
        public double getDouble(Object var1, int var2) {
            return this.getDouble(var1, (long)var2);
        }
    
        /** @deprecated */
        @Deprecated
        public void putDouble(Object var1, int var2, double var3) {
            this.putDouble(var1, (long)var2, var3);
        }
    

    下面这些方法工作于处于c堆的变量

    读操作

    类似的,用于从给定的内存地址中获取值,如果给定的地址是0或者指向的地址不是一个通过下面的allocateMemory获取到的块,那么结果可能是未定义的。

    写操作

    把给定的元素写入给定的内存地址。如果给定的内存地址是0或者没有指向一个通过下面的allocateMemory获取到的块,结果可能是未定义的。

        public native byte getByte(long var1);
    
        public native void putByte(long var1, byte var3);
    
        public native short getShort(long var1);
    
        public native void putShort(long var1, short var3);
    
        public native char getChar(long var1);
    
        public native void putChar(long var1, char var3);
    
        public native int getInt(long var1);
    
        public native void putInt(long var1, int var3);
    
        public native long getLong(long var1);
    
        public native void putLong(long var1, long var3);
    
        public native float getFloat(long var1);
    
        public native void putFloat(long var1, float var3);
    
        public native double getDouble(long var1);
    
        public native void putDouble(long var1, double var3);
    
       
    

    通过给定的内存地址,获得本地指针,如果给定的地址是0或者没有指向一个通过下面的allocateMemory获取到的块,结果是未定义的

    如果本地指针的的宽度不够64位,那么这个地址会作为一个无符号数扩展到Java的long型。指针可以通过任何给定字节数的偏移量来索引,只需简单地把这个偏移量加到java的long型上。真正读取目标地址使用的字节数可以通过调用下面addresssize方法获取(实际上也就是vm的位数,如4个字节或者8个字节对应32位和64位)

        public native long getAddress(long var1);
    
    

    把本地指针存到给定的内存地址中,如果地址是0,或者指向的块不是一个通过allocateMemory获取的块,那么结果可能是未定义的。

    同上一样,真正写入的目标地址的字节数可以通过addressSize获取。

    
        public native void putAddress(long var1, long var3);
    

    下面的方法是封装了c语言对应的malloc, realloc, free方法以及做出的一些增补

    分配一个新的本地内存块,大小是给定的数量的字节数。内存中的内容是未初始化的,一般来说是无意义的垃圾数据。返回的本地指针不能是0,并且和所有的元素类型对齐。通过调用freeMemory方法释放这些内存,或者通过reallocateMemory来重新调整大小。

    如果大小是非法的或者对于本地类型的大小太大,会抛出IllegalArgumentException

    当系统拒绝分配内存的时候会抛出OutOfMemoryError

    public native long allocateMemory(long bytes);
    

    重新在内存中增加给定数量字节数的新内容,这些位于之前块后面的新块都是未经初始化的,通常数据内容都是没有意义的垃圾数据。当且仅当请求的块大小为0的时候返回0。这个结果的本地指针也是一样和所有元素类型对齐。通过freeMemory方法释放这些内存,或者通过reallocateMemory来重新调整大小。这个方法中如果给出的address为null,这个时候方法会执行分配方法,相当于退化为上一个方法了

    异常情况同上

    public native long reallocateMemory(long address, long bytes);
    

    从1.7开始新增了一些方法如下以及一些封装了这些方法的调用接口。

    Native方法将给定内存块中的所有字节设置为固定值(通常是0)

    这个方法通过两个参数确定一个块的base地址,所以跟前面讨论的一样,因此这个方法提供了一个双寄存器寻址方式。当Object的引用为null的时候,偏移量offset就提供了一个绝对的base地址。

    存储是连续(原子)的单元,由地址和长度两个参数确定。如果有效地址和长度都是8的倍数,这个时候都是用long存储,如果有效地址和长度分别能被4或者2整除,则以int或者short来存储。

    封装的方法等效于setMemory(null, address, bytes, value),也就是Object为null的情况。

     public native void setMemory(Object o, long offset, long bytes, byte value);
     
     public void setMemory(long address, long bytes, byte value) {
             setMemory(null, address, bytes, value);
      }
    

    把目标块的存储内容拷贝到另一个块中。跟上面描述的一样,目标块和源块都是用两个参数确定的,所以也都和上面一样提供双寄存器的寻址方式。当Object的引用为null的时候,offset提供一个绝对的base地址。

    跟上面一样,这个传输都是在连续(原子)的单元中进行,这写单元通过地址和长度共同决定。如果有效地址和长度都是8的倍数,这个时候都是用long存储,如果有效地址和长度分别能被4或者2整除,则以int或者short来存储。

    这个方法也是1.7增加的

    封装方法等效于copyMemory(null, srcAddress, null, destAddress, bytes)也就是Object为null的情况

    public native void copyMemory(Object srcBase, long srcOffset,
                                     Object destBase, long destOffset,
                                      long bytes);
                                      
    public void copyMemory(long srcAddress, long destAddress, long bytes) {
              copyMemory(null, srcAddress, null, destAddress, bytes);
     }                                
    

    释放一个通过allocateMemory或者reallocateMemory获取的本地内存块,如果传递的地址为null,此时不做任何操作。

    public native void freeMemory(long address);
    

    下面是一些随机查找方法

    返回指定字段的偏移量,转换为32位的,也就是int型,这个方法实现如下
    注意:这个方法是封装了long返回的方法

    @Deprecated
      public int fieldOffset(Field f) {
            //Field.getModifiers()方法可以获取修饰符(一个十六进制)
            //判断是不是静态修饰可以用Modifier类中的相关静态方法来判断
            if (Modifier.isStatic(f.getModifiers()))
                //调用staticFieldOffset获取给定的静态Field域的偏移量
                return (int) staticFieldOffset(f);
            else
                //调用objectFieldOffset获取给定Field域的偏移量
                return (int) objectFieldOffset(f);
      }
    

    返回访问指定类的静态字段的base地址,实现如下
    注意:从1.4开始使用staticFieldBase来实现读取特定的Field来读取静态域的base。这个方法只能工作在把所有静态域存在一起的JVM。

    @Deprecated
    public Object staticFieldBase(Class c) {
           //读取所有Class中的field
           Field[] fields = c.getDeclaredFields();
           //循环读取Field
           for (int i = 0; i < fields.length; i++) {
                //找到第一个static字段后,用这个字段计算base地址
               if (Modifier.isStatic(fields[i].getModifiers())) {
                   return staticFieldBase(fields[i]);
               }
           }
           //没有静态字段
           return null;
    }
    

    报告给定的静态Field在它所属的类申请的空间中的位置。不要在这个偏移量上进行任何的算数。
    因为这个结果只是一个传递给unsafe 堆的访问器用的cookie。

    任何给定的字段都始终具有相同的偏移量和base,同一个类的任何两个不同字段都没有相同的偏移量和base

    注:从1.4开始,Field的偏移量用long标示,尽管sun的JVM没有使用32位的形式来表示这个地址,但是这样对于将静态字段存储在绝对地址的jvm实现,可以直接利用类似getInt(object,long)的调用形式,通过null和long来定位字段,因此,对于需要将代码移植到64位平台的情况来说,保留这种完整的偏移量无疑更加好用。

    public native long staticFieldOffset(Field f);
    

    这个方法报告了一个字段相对对象的偏移地址,跟上面说的一样,也不要尝试在这个偏移量上进行算术运算。

    注意:出了跟上面一样值得注意的地方以外,这个地方返回的相对偏移量是个long主要是为了保证前后连贯,毕竟如果没有这一层考虑,很难想象出哪个对象的偏移量居然需要那么多bits才能表示出来。

    public native long objectFieldOffset(Field f);
    

    通过给定字段获取Object对象(如果存在的话),结合这个方法通过getInt等方法可以获取静态字段

    注意,这个方法的返回对象可能是null,也可能是一个对象,但是这个对象也不是完整可信的,这也只是一个cookie,不要把它当做一个真实的对象,所以不要以任何方式使用它,除非是在本类中的 get和put类的方法中。

    public native Object staticFieldBase(Field f);
    

    这个方法用于确保给点大哥class已经被初始化了,这个方法通常用于在获取一个类静态字段的base之前

    public native void ensureClassInitialized(Class c);
    

    报告给定数组类的第一个元素在存储中所在的位置。
    如果arrayIndexScale方法返回的值不为零,那么可以通过这个base和scale一起计算出新的偏移量,然后访问给定数组的指定元素。

    public native int arrayBaseOffset(Class arrayClass);
    

    读取给定数组类在存储中的每个元素的偏移量,但是窄类型的数组通常不能跟类似getByte这种方法一起工作,所以这种类返回的这单个元素的偏移量就是0.(窄类型我的理解是类似接口创建的数组,没有初始化的这种)

    public native int arrayIndexScale(Class arrayClass);
    

    返回本地指针的字节数,这个值可能是4或者8,4代表32位,8代表64位。注意其他基本类型的地址大小完全由他们的存储的内容决定

    public native int addressSize();
    

    这个方法以字节为单位返回本机内存页的大小,这个数字通常是2的幂

    
     public native int pageSize();
    

    下面是一些JNI的随机可信操作

    这个方法告知虚拟机定义一个类,并且不进行安全检查。默认情况下类加载器和保护域来自调用类

    public native Class defineClass(String name, byte[] b, int off, int len,
                                         ClassLoader loader,
                                         ProtectionDomain protectionDomain);
                                         
     public native Class defineClass(String name, byte[] b, int off, int len);
    

    这个方法定义一个类,但是不让类加载器和系统字典知道这个类。注意这个类跟真正匿名类有点不一样。这个方法在java.lang.invoke.InnerClassLambdaMetafactory中有应用,这个类的源码找的时候可以通过LambdaMetafactory这个类点过来

    这里有个词叫CP应该指的是Constant Pool

    对于每一个CP的项,对应的CP patch要么是null要么就必须和标签匹配

    对于Integer, Long, Float, Double:就是java.lang下面的对应封装类型
    Utf8:就是字符串(如果用于名字或者签名,语法上必须正确)
    Class:任何java.lang.Class的对象
    String:任何对象(不单纯是java.lang.String)
    InterfaceMethodRef:在调用点的参数上执行方法的句柄

    参数中
    hostClass 是宿主类,也就是链接、访问控制、保护域和类加载器的上下文
    data 是类文件的字节码
    cpPathces 就是非空的cp项了,用来替换data中对应的cp

    这个方法可以应用于需要动态生成很多结构相同,只不过有一些变量是不同的类这种场景
    这个方法构造出来的类智能通过返回的Class反射来进行操作

    关于这个方法给一个参考链接,知乎上一个回答

    public native Class defineAnonymousClass(Class hostClass, byte[] data, Object[] cpPatches);
    

    通过给定的Class对象创建一个类的示例,不执行构造过程,如果类没有初始化,执行初始化。

    public native Object allocateInstance(Class cls)   throws InstantiationException;
    

    锁定给定的对象,解锁必须通过monitorExit方法

    public native void monitorEnter(Object o);
    

    解锁给定的对象,前提是必须是加锁了的对象

    public native void monitorExit(Object o);
    

    尝试对对象进行加锁。返回的true和false标示了加锁是否成功,如果成功了这个对象就算是加上锁了。

     public native boolean tryMonitorEnter(Object o);
    

    在不通过校验器的情况下抛出一个异常

    
    public native void throwException(Throwable ee);
    

    CAS操作
    原子操作更新变量x,如果当前对象o是(持有)expected,将对象更新为x

    其中o为目标对象,offset为偏移地址,expected为期待的值,x为目标值

    更新成功返回true,失败返回false

    其他几个类似

    public final native boolean compareAndSwapObject(Object o, long offset,
                                                          Object expected,
                                                          Object x);
                                                          
    public final native boolean compareAndSwapInt(Object o, long offset,
                                                         int expected,
                                                         int x);
                 
     public final native boolean compareAndSwapLong(Object o, long offset,
                                                        long expected,
                                                        long x);
    

    下面的都是上面提到过的getXXX的方法的Volatile版本。get方法获取给定的java变量的值,附加了volatile加载的语义
    put方法存储一个值到给定的java变量同样附加了volatile存储语义

    public native Object getObjectVolatile(Object var1, long var2);
    
        public native void putObjectVolatile(Object var1, long var2, Object var4);
    
        public native int getIntVolatile(Object var1, long var2);
    
        public native void putIntVolatile(Object var1, long var2, int var4);
    
        public native boolean getBooleanVolatile(Object var1, long var2);
    
        public native void putBooleanVolatile(Object var1, long var2, boolean var4);
    
        public native byte getByteVolatile(Object var1, long var2);
    
        public native void putByteVolatile(Object var1, long var2, byte var4);
    
        public native short getShortVolatile(Object var1, long var2);
    
        public native void putShortVolatile(Object var1, long var2, short var4);
    
        public native char getCharVolatile(Object var1, long var2);
    
        public native void putCharVolatile(Object var1, long var2, char var4);
    
        public native long getLongVolatile(Object var1, long var2);
    
        public native void putLongVolatile(Object var1, long var2, long var4);
    
        public native float getFloatVolatile(Object var1, long var2);
    
        public native void putFloatVolatile(Object var1, long var2, float var4);
    
        public native double getDoubleVolatile(Object var1, long var2);
    
        public native void putDoubleVolatile(Object var1, long var2, double var4);
    

    接下来是putObjectVolatile这个方法的一个版本,不能保证存储对其他线程的立即可见,也就是说这个更改是有延迟的。这个方法通常只有在底层字段是volatile修饰的时候才能生效(除非是数组元素)
    其他两个类似

     public native void    putOrderedObject(Object o, long offset, Object x);
     
     public native void    putOrderedInt(Object o, long offset, int x);
     
     public native void    putOrderedLong(Object o, long offset, long x);
    

    下面是关于线程的方法

    解除被park阻塞的线程的阻塞,或者如果这个线程并没有被阻塞,会导致后续的对于park的调用无法成功阻塞

    注意:这个操作并不安全,不安全主要是由于调用方必须以某种方式确保线程没有被销毁。单纯在Java中判断线程存活通常不需要做什么特别的操作来确保(线程的引用通常都是存活的),但是在原生中这几乎是不可能自动完成的

     public native void unpark(Object thread);
    

    阻塞当前线程,直到unpark被调用,或者在这个park方法之前调用过unpark方法,这就意味着unpark已经出现过了,或者线程中断或者time的时间到了。

    在time不为0的情况下,如果isAbsolute为true,time就是相当于时间戳的毫秒数,如果isAbsolute为falsetime就表示纳秒,或者特殊情况可能会出现无任何理由的返回。

    关于这个方法的使用可以参考源码中java.util.concurrent.locks.LockSupport类,关于park的支持基本上底层都是unsafe的park方法

    public native void park(boolean isAbsolute, long time);
    

    这个方法可以获取系统的平均负载值,具体来说就是系统运行队列分配给可用处理器的平均负载。

    这个方法检索nelems数量的样本,loadavg这个double型的数组存放对应负载值的结果,注意系统最多可以取三种样本值,也就是nelems只能取三种1,2,3分别对应1,5,15分钟内系统的样本。

    如果无法获取系统的负载,这个方法会返回-1,否则则返回获取到的样本数量,也就是loadavg中有效的元素个数

    public native int getLoadAverage(double[] loadavg, int nelems);
    

    下面是JDK1.8增加的内存屏障相关内容,

    //这个方法前面的所有读操作一定在load屏障之前执行完成
    public native void loadFence();
    //这个方法之前的所有写操作,一定在store屏障之前执行完成
    public native void storeFence();
    //这个方法之前的读写操作,都在full屏障之前执行完成
    public native void fullFence();
    

    下面是1.8加的getAndAdd系列方法

    基于CAS和volatile系列方法组合出来的给某个元素添加值(或者设置值)得方法

    public final int getAndAddInt(Object o, long offset, int delta) {
            int v;
            do {
                v = getIntVolatile(o, offset);
            } while (!compareAndSwapInt(o, offset, v, v + delta));
            return v;
        }
        
     public final long getAndAddLong(Object o, long offset, long delta) {
            long v;
            do {
                v = getLongVolatile(o, offset);
            } while (!compareAndSwapLong(o, offset, v, v + delta));
            return v;
        }
        
        public final int getAndSetInt(Object o, long offset, int newValue) {
            int v;
            do {
                v = getIntVolatile(o, offset);
            } while (!compareAndSwapInt(o, offset, v, newValue));
            return v;
        }
    
        public final long getAndSetLong(Object o, long offset, long newValue) {
            long v;
            do {
                v = getLongVolatile(o, offset);
            } while (!compareAndSwapLong(o, offset, v, newValue));
            return v;
        }
        
        public final Object getAndSetObject(Object o, long offset, Object newValue) {
            Object v;
            do {
                v = getObjectVolatile(o, offset);
            } while (!compareAndSwapObject(o, offset, v, newValue));
            return v;
        }
    
    

    还有最后给虚拟机用的抛出非法访问异常的方法

    
    private static void throwIllegalAccessError() {
           throw new IllegalAccessError();
        }
    

    下面是一些参考的内容:
    JAVA中神奇的双刃剑--Unsafe
    在openjdk8下看Unsafe源码
    Java魔法类:sun.misc.Unsafe

    相关文章

      网友评论

        本文标题:【Java源码计划】Unsafe<rt.jar_sun.m

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