美文网首页JUC源码分析
Unsafe类源码解析

Unsafe类源码解析

作者: 秦老厮 | 来源:发表于2019-08-26 14:39 被阅读0次

    前言

    Unsafe,顾名思义,一个不安全的类,那么jdk的开发者为什么要设计一个不安全的类呢?这个类为什么会不安全呢?现在就让我们来揭开Unsafe类的神秘面纱。

    1.概述

    作为java开发者的我们都知道,java是没有指针的,默认是由JVM进行内存的分配与垃圾回收,那就意味着java不能直接操作内存了?其实不是的,Unsafe类通过JNI的方式访问本地的C++实现库从而使java具有了直接操作内存空间的能力,但这同时也带来了一定的问题,如果不合理地使用Unsafe类操作内存空间,可能导致内存泄漏,是很危险的,这就要看开发者对Unsafe类的熟悉程度了,不是非常熟悉这个类,不推荐使用。而且java官方也是不推荐开发者使用的,官方关于Unsafa的文档很少,并且这个类在jdk8中也不开源,在Openjdk中也是不开源的,只能通过IDE反编译来看它的源码,它的源码中好多的方法都标注了@Deprecated注解,而且也没有注释(也许是反编译过来看不到注释)。(之前传说jdk9要这个类删除,现在jdk12都出来了,这个类不但没有删除,还在jdk.internal.misc包下增加了一个Unsafe类。并且自从jdk9开始,Unsafe类也开源了,与sun.misc.Unsafe不同的是,jdk.internal.misc.Unsafe是不开放给开发者的,应该是供jdk的开发和维护人员使用的)。既然官方不推荐使用,那为什么还要设计这个类呢?因为Unsafe提供了硬件级别的原子性操作,JUC包中用到的CAS算法就是Unsafe类提供的,jdk的开发者用这个类实现了JUC包中的一部分核心功能。同时,Unsafe类就像是java给开发者提供的一个后门,让开发者使用Unsafe就可以在任意内存地址读写数据,而不用经过JVM的调度,也使java更加地灵活,具有一定的C和C++访问内存的能力,对于系统调优有一定的帮助,对于优秀的开发者,也可以使用这个类造一些实用的轮子。可见,Unsafe类还是有一定的用处的,不过在使用时需要小心谨慎。

    2.获取Unsafe

    2.1Unsafe类的构造器是私有的,采用了单例模式,提供了一个静态方法,是不是可以通过这个静态方法获取到Unsafe对象呢?

    public class UnsafeTest {
    
       public static void main(String[] args) {
           Unsafe unsafe = Unsafe.getUnsafe();
           System.out.println(unsafe);
       }
    }
    
    执行结果

    报错了,Unsafe本身提供了获取实例的静态方法,那为什么我们在使用时会报错呢?

    看下源码(jdk11的,jdk8不开源,反编译过来的不太好看):

    private Unsafe() {}
    
    private static final Unsafe theUnsafe = new Unsafe();
    
    @CallerSensitive
    public static Unsafe getUnsafe() {
       Class<?> caller = Reflection.getCallerClass();
       if (!VM.isSystemDomainLoader(caller.getClassLoader()))
           throw new SecurityException("Unsafe");
       return theUnsafe;
    }
    

    看到源码,是不是上面的异常有眉目了?就是throw new SecurityException("Unsafe");这句抛出的异常,我们看为什么抛出异常:

    if (!VM.isSystemDomainLoader(caller.getClassLoader()))
           throw new SecurityException("Unsafe");
    

    抛出异常就说明不满足if条件,isSystemDomainLoader(caller.getClassLoader())方法就是检查调用者的类加载器是否是启动类加载器,抛出异常就是因为我们自己创建的类不是由启动类加载器加载,而是默认由系统类加载器加载的,故抛出异常。

    2.2使用反射获取Unsafe实例

    public class UnsafeTest {
    
       public static void main(String[] args) {
           
           final Field theUnsafe;
           try {
               theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
               theUnsafe.setAccessible(true);
               Unsafe unsafe  = (Unsafe) theUnsafe.get(null);
               System.out.println(unsafe.getClass().getClassLoader());
               System.out.println(unsafe);
           } catch (NoSuchFieldException | IllegalAccessException e) {
               e.printStackTrace();
           }
       }
    }
    
    执行结果

    发现使用反射获取Unsafe实例是可以获取到的,打印的类加载器的信息为null,说明是由启动类加载加载的。

    2.3jdk11的jdk.internal.misc包中的Unsafe类的getUnsafe静态方法中没有要求调用者必须要由启动类加载器加载,源码如下:

    private Unsafe() {}
    
    private static final Unsafe theUnsafe = new Unsafe();
    
    public static Unsafe getUnsafe() {
       return theUnsafe;
    }
    

    这回是不是可以直接获取了呢?试一下:

    public class UnsafeTest {
    
       public static void main(String[] args) {
           jdk.internal.misc.Unsafe unsafe = Unsafe.getUnsafe();
           System.out.println(unsafe);
       }
    }
    

    又报错了,异常信息如下:

    Exception in thread "main" java.lang.IllegalAccessError: class hello.UnsafeTest (in unnamed module @0x6e8dacdf) cannot access class jdk.internal.misc.Unsafe (in module java.base) because module java.base does not export jdk.internal.misc to unnamed module @0x6e8dacdf
       at hello.UnsafeTest.main(UnsafeTest.java:9)
    

    意思就是这个类没有开放给我们,我们不能访问。

    看一下java.base下的module-info.java文件,可以看到这个Unsafe类确实没有开放给我们,只是开放给以下的jdk的部分模块:

    exports jdk.internal.misc to
       java.desktop,
       java.logging,
       java.management,
       java.naming,
       java.net.http,
       java.rmi,
       java.security.jgss,
       java.sql,
       java.xml,
       jdk.attach,
       jdk.charsets,
       jdk.compiler,
       jdk.internal.vm.ci,
       jdk.jfr,
       jdk.jlink,
       jdk.jshell,
       jdk.net,
       jdk.scripting.nashorn,
       jdk.scripting.nashorn.shell,
       jdk.unsupported;
    

    看来直接获取是获取不到的,除非修改openjdk11源码中的module-info.java文件,将jdk.internal.misc这个包开放出来,重新编译。不过作为普通开发者是没有必要修改源码的,通过反射获取就可以了。

    jdk11开放给开发者的也是sun.misc包中的Unsafe类,这个包在jdk.unsupported模块下。也是需要使用反射获取实例的。

    注:以下涉及到的源码都为java11的源码。

    3.重要API分析

    Student类:

    public class Student {
        private String name;
        private int age;
        public static String info;
    
       static {
           info = "这是一个优秀的学生";
       }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public Student(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        public Student() {
        }
    }
    

    开放给我们能用的位于sun.misc包下的Unsafe类的实现其实是调用了更加底层的位于jdk.internal.misc包下的Unsafe类,native方法就是位于这个类中,这点与java8的Unsafe类不同,java8只有一个Unsafe类,这个类中大多都为native方法:

    sun.misc.Unsafe源码:

    private static final Unsafe theUnsafe = new Unsafe();
    private static final jdk.internal.misc.Unsafe theInternalUnsafe = jdk.internal.misc.Unsafe.getUnsafe();
    

    3.1获取对象字段或静态属性的内存地址偏移量

    源码如下:

    //获取对象属性的便宜量
    @ForceInline
    public long objectFieldOffset(Field f) {
        return theInternalUnsafe.objectFieldOffset(f);
    }
    //获取静态属性的偏移量
    @ForceInline
    public long staticFieldOffset(Field f) {
        return theInternalUnsafe.staticFieldOffset(f);
    }
    

    举个栗子:

    @Test
    public void testGetFieldOffset() throws NoSuchFieldException, IllegalAccessException {
        final Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        Unsafe unsafe = (Unsafe) theUnsafe.get(null);
    
        //获取字段偏移量
        long address = unsafe.objectFieldOffset(Student.class.getDeclaredField("name"));
        System.out.println(address);
    
        //获取静态字段偏移量
        long staticAddress = unsafe.staticFieldOffset(Student.class.getDeclaredField("info"));
        System.out.println(staticAddress);
    }
    

    3.2内存操作,包括内存的分配,释放,复制

    3.2.1分配内存:

    //参数为需要分配的内存的字节数
    public long allocateMemory(long bytes) {
        return theInternalUnsafe.allocateMemory(bytes);
    }
    

    3.2.2重新分配内存(内存扩展):

    //第一个参数为已经分配的内存,第二个参数为要扩展的字节数
    public long reallocateMemory(long address, long bytes) {
       return theInternalUnsafe.reallocateMemory(address, bytes);
    }
    

    3.2.3内存释放:

    public void freeMemory(long address) {
        theInternalUnsafe.freeMemory(address);
    }
    

    举个栗子:

    @Test
    public void testMemoryOperate() throws NoSuchFieldException, IllegalAccessException {
        final Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        Unsafe unsafe = (Unsafe) theUnsafe.get(null);
        //分配内存
        long address = unsafe.allocateMemory(4);
        System.out.println(address);
        //扩展内存
        long newAddress = unsafe.reallocateMemory(address, 8);
        System.out.println(newAddress);
        //释放内存
        unsafe.freeMemory(newAddress);
    }
    

    关于内存操作还有几个方法,这里列出,不做举例,这几个方法笔者平时不常用,源码中也不常用。

    3.2.4设置内存:将给定的内存设置为固定值

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

    3.2.5内存复制:

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

    3.3内存屏障
    为了保证内存的可见性,java编译器在生成指令序列的适当位置会插入内存屏障指令类禁止特定类型的处理器重排序,java内存模型(JMM)把内存屏障指令分为4类:

    • LoadLoad(Load1,LoadLoad,Load2):确保load1数据的装载先于load2及所有后序装载指令的装载。
    • LoadStore(Load1,LoadStore,Store2):确保Load1数据的装载先于Store2及所有后序存储指令刷新内存。
    • StoreStore(Store1,StoreStore,Store2):确保Store1数据刷新内存先于Store2及所有后序存储指令刷新内存。
    • StoreLoad(Store1,StoreLoad,Load2):确保Store1数据刷新内存先于Load2及所有后序装载指令的装载。该屏蔽指令会使该屏蔽之前的所有内存访问指令执行完成后才执行屏蔽之后的内存访问指令。并且这个指令是一个全能的指令,同时具备以上三个内存屏蔽指令的功能。
    //确保在屏蔽之前加载(读操作),屏蔽之后的读写操作不会重排序,相当于LoadLoad 加上 LoadStore 屏障
    public void loadFence() {
       theInternalUnsafe.loadFence();
    }
    //确保在屏蔽之前加载和存储(读写操作),屏蔽之后的写操作不会重排序,相当于StoreStore 加上 LoadStore 屏障
    public void storeFence() {
       theInternalUnsafe.storeFence();
    }
    //确保在屏蔽之前读写操作,屏蔽之后的读写操作不会重排序,同时具有以上两个方法的功能,还具有StoreLoad屏蔽的功能
    public void fullFence() {
       theInternalUnsafe.fullFence();
    }
    

    3.4通过操作内存从一个给定的java变量获取或设置值

    这类方法有针对于基本数据类型和对象类型(下面列出比分源码):

    //从基本数据类型变量获取值
    public int getInt(Object o, long offset) {
        return theInternalUnsafe.getInt(o, offset);
    }
    //设置基本类型变量的值
    public void putInt(Object o, long offset, int x) {
        theInternalUnsafe.putInt(o, offset, x);
    }
    //从对象类型变量获取值
    public Object getObject(Object o, long offset) {
        return theInternalUnsafe.getObject(o, offset);
    }
    //设置对象类型变量的值
    public void putObject(Object o, long offset, Object x) {
        theInternalUnsafe.putObject(o, offset, x);
    }
    

    栗子(以int类型为例):

    @Test
    public void testGetAndPutInt() throws NoSuchFieldException, IllegalAccessException {
        final Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        Unsafe unsafe = (Unsafe) theUnsafe.get(null);
    
        Student student = new Student("liming",16);
        //通过objectFieldOffset方法获取学生年龄在内存中的偏移量
        long offset = unsafe.objectFieldOffset(Student.class.getDeclaredField("age"));
        System.out.println(offset);
        //通过操作内存的方式获取学生年龄
        int anInt = unsafe.getInt(student, offset);
        System.out.println(anInt);
        System.out.println(student.getAge());
        //通过操作内存的方式修改学生年龄
        unsafe.putInt(student,offset,30);
        System.out.println(student.getAge());
    }
    

    栗子(以Object类型为例):

    @Test
    public void testGetAndPutObject() throws NoSuchFieldException, IllegalAccessException {
       final Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
       theUnsafe.setAccessible(true);
       Unsafe unsafe = (Unsafe) theUnsafe.get(null);
    
       Student student = new Student("liming",16);
       //通过objectFieldOffset方法获取学生姓名在内存中的偏移量
       long offset = unsafe.objectFieldOffset(Student.class.getDeclaredField("name"));
       //获取对象属性
       System.out.println(unsafe.getObject(student,offset));
       System.out.println(student.getName());
       //修改对象属性
       unsafe.putObject(student,offset,"xiahua");
       System.out.println(student.getName());
    }
    

    3.5通过操作内存从一个内存地址获取或设置值

    这类方法和3.4一样,有基本数据类型和对象类型,这里以int类型为例举个栗子:

    //从一个内存地址获取整型值
    public int getInt(long address) {
        return theInternalUnsafe.getInt(address);
    }
    //给一个内存地址设置一个整型值
    public void putInt(long address, int x) {
        theInternalUnsafe.putInt(address, x);
    }
    

    栗子:

    @Test
    public void testGetAndPutInt2() throws NoSuchFieldException, IllegalAccessException {
        final Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        Unsafe unsafe = (Unsafe) theUnsafe.get(null);
    
        //通过allocateMemory方法分配一个8具有8个字节的内存空间
        long address = unsafe.allocateMemory(8);
    
        //给指定的内存空间写入一个整型值
        unsafe.putInt(address,60);
        //获取内存空间的值
        System.out.println(unsafe.getInt(address));
    
        //重新分配内存,扩大到16字节
        final long newAddress = unsafe.reallocateMemory(address, 16);
        unsafe.putInt(newAddress,100);
        System.out.println(unsafe.getInt(newAddress));
    
        //回收分配的内存空间
        unsafe.freeMemory(newAddress);
    }
    

    3.6通过操作内存从一个给定的变量获取或设置一个具有volatile语义的值

    这类方法也和3.4一样,有基本数据类型和对象类型,其实就是volatile版的3.4方法,这里以int类型为例:

    //从一个变量获取具有volatile语义的整型值
    public int getIntVolatile(Object o, long offset) {
        return theInternalUnsafe.getIntVolatile(o, offset);
    }
    //给一个变量设置具有volatile语义的值
    public void putIntVolatile(Object o, long offset, int x) {
        theInternalUnsafe.putIntVolatile(o, offset, x);
    }
    

    栗子:

    @Test
    public void testGetAndPutIntVolatile() throws NoSuchFieldException, IllegalAccessException {
        final Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        Unsafe unsafe = (Unsafe) theUnsafe.get(null);
    
        long offset = unsafe.objectFieldOffset(Student.class.getDeclaredField("age"));
    
        //设置学生的年龄,使用volatile语义,设置后立马更新到内存对其他线程可见。
        unsafe.putIntVolatile(Student.class,offset,32);
        System.out.println(unsafe.getIntVolatile(Student.class,offset));
    }
    

    3.7通过操作内存从一个给定的变量惰性地设置一个具有volatile语义的值

    这类方有三个,有基本数据类型和对象类型,其实就是lazy加volatile版的3.4方法,这里以int类型为例:

    public void putOrderedObject(Object o, long offset, Object x) {
        theInternalUnsafe.putObjectRelease(o, offset, x);
    }
    public void putOrderedInt(Object o, long offset, int x) {
        theInternalUnsafe.putIntRelease(o, offset, x);
    }
    public void putOrderedLong(Object o, long offset, long x) {
        theInternalUnsafe.putLongRelease(o, offset, x);
    }
    

    栗子:

    /*
    *PutIntVolatile的lazy版,惰性设值,设值后内存不能保证立即对其他线程可见,并且变量和后序内存可以重排序
    */
        @Test
        public void testPutIntVolatileLazy() throws NoSuchFieldException, IllegalAccessException {
            final Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafe.setAccessible(true);
            Unsafe unsafe = (Unsafe) theUnsafe.get(null);
    
            long offset = unsafe.objectFieldOffset(Student.class.getDeclaredField("age"));
    
            unsafe.putOrderedInt(Student.class,offset,30);
            System.out.println(unsafe.getIntVolatile(Student.class,offset));
        }
    

    3.8通过操作内存获取或设置数组元素值

    //获取数组中第一个元素在内存的偏移地址
    public int arrayBaseOffset(Class<?> arrayClass) {
        return theInternalUnsafe.arrayBaseOffset(arrayClass);
    }
    //获取数组增量元素在内存中的地址
    public int arrayIndexScale(Class<?> arrayClass) {
        return theInternalUnsafe.arrayIndexScale(arrayClass);
    }
    

    栗子:

    @Test
    public void testArrayOperate() throws NoSuchFieldException, IllegalAccessException {
        final Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        Unsafe unsafe = (Unsafe) theUnsafe.get(null);
    
        int[] array = new int[]{1,5,8,9,6,4,7};
    
        //获取数组中第一个元素在内存的偏移地址
        int offset = unsafe.arrayBaseOffset(array.getClass());
        //获取数组增量元素在内存中的地址
        int indexScale = unsafe.arrayIndexScale(array.getClass());
    
        //通过操作内存地址获取数组的第一个元素
        System.out.println(unsafe.getInt(array,offset));
        //通过操作内存修改数组的第一个元素
        unsafe.putInt(array,offset,3);
        System.out.println(unsafe.getInt(array,offset));
        //通过操作内存获取数组的第三个元素,arrayBaseOffset和arrayIndexScale共同使用,就可以获得任意一个数组元素在内存的地址
        System.out.println(unsafe.getInt(array,indexScale*2+offset));
    
        //通过循环遍历数组元素
        for (int i = 0; i < array.length; i++) {
            System.out.print(unsafe.getInt(array,i*indexScale+offset) + " ");
        }
    }
    

    3.9从一个给定的内存地址获取或设置一个本地指针

    //从给定的内存地址获取一个本地指针
    public long getAddress(long address) {
        return theInternalUnsafe.getAddress(address);
    }
    //从给定的内存地址设置一个本地指针
    public void putAddress(long address, long x) {
        theInternalUnsafe.putAddress(address, x);
    }
    

    栗子:

    @Test
    public void testGetAndPutAddress() throws NoSuchFieldException, IllegalAccessException {
        final Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        Unsafe unsafe = (Unsafe) theUnsafe.get(null);
    
        //通过allocateMemory方法分配一个8具有8个字节的内存空间
        long address = unsafe.allocateMemory(8);
    
        //给这个内存中存放一个本地指针
        unsafe.putAddress(address,200);
        //获取指定内存地址的指针
        System.out.println(unsafe.getAddress(address));
        //打印通过putAddress存放的本地指针的大小
        System.out.println(unsafe.addressSize());
        //打印内存页面的大小,通常为4k,即4096   //关于内存页面不了解的可以看下操作系统相关的知识
        System.out.println(unsafe.pageSize());
        //释放内存
        unsafe.freeMemory(address);
    }
    

    3.10硬件级别的CAS操作

    关于CAS算法不了解的小伙伴可以参考我的另一篇文章:CAS算法

    //CAS操作对象类型
    public final boolean compareAndSwapObject(Object o //对象, long offset //内存偏移量,
                                              Object expected, //预期值
                                              Object x) { //新值
        return theInternalUnsafe.compareAndSetObject(o, offset, expected, x);
    }
    //CAS操作int类型
    public final boolean compareAndSwapInt(Object o, long offset,
                                           int expected,
                                           int x) {
        return theInternalUnsafe.compareAndSetInt(o, offset, expected, x);
    }
    //CAS操作long型
    public final boolean compareAndSwapLong(Object o, long offset,
                                            long expected,
                                            long x) {
        return theInternalUnsafe.compareAndSetLong(o, offset, expected, x);
    }
    

    关于CAS操作,Unsafe源码只给出以上三个方法,其他类型的CAS操作可以转化为以上三种类型,比如boolean类型的CAS操作可以转化为int型的CAS操作。

    栗子:

    @Test
    public void testCAS() throws NoSuchFieldException, IllegalAccessException {
        final Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        Unsafe unsafe = (Unsafe) theUnsafe.get(null);
    
        long offset = unsafe.objectFieldOffset(Student.class.getDeclaredField("age"));
    
        unsafe.putInt(Student.class,offset,25);
        System.out.println(unsafe.getInt(Student.class,offset));
        //通过Unsafe提供的硬件级别的原子操作 CAS算法更新学生的年龄
        unsafe.compareAndSwapInt(Student.class,offset,24,35);
        System.out.println(unsafe.getInt(Student.class,offset));
    
        unsafe.compareAndSwapInt(Student.class,offset,25,35);
        System.out.println(unsafe.getInt(Student.class,offset));
    }
    

    3.11线程的挂起与恢复

    Unsafe类提供了线程的挂起与恢复方法,JUC中的锁会用到Unsafe提供的线程挂起与恢复方法,源码如下:

    //线程挂起,直到unpark调用或者打断或者超时,线程恢复。
    //isAbsolute设置是否为绝对时间,如果为true,则超时时间的单位为毫秒
    //isAbsolute为false,time设置为0,就是一直阻塞,如果time不为0,则超时时间的单位为纳秒
    public void park(boolean isAbsolute, long time) {
       theInternalUnsafe.park(isAbsolute, time);
    }
    //线程恢复
    public void unpark(Object thread) {
       theInternalUnsafe.unpark(thread);
    }
    

    栗子:

    @Test
    public void testParkAndUnPark() throws NoSuchFieldException, IllegalAccessException {
        final Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        Unsafe unsafe = (Unsafe) theUnsafe.get(null);
    
        //写一个线程执行一个任务
        Thread thread = new Thread(() -> System.out.println("hello"));
        thread.start();
        unsafe.unpark(Thread.currentThread());  //如果不加这句,线程或一直处于阻塞状态
        unsafe.park(false,0);
    }
    

    3.12从一个变量获取值并让它自动增加给定的值

    public final int getAndAddInt(Object o, long offset, int delta) {
       return theInternalUnsafe.getAndAddInt(o, offset, delta);
    }
    
    public final long getAndAddLong(Object o, long offset, long delta) {
       return theInternalUnsafe.getAndAddLong(o, offset, delta);
    }
    

    举个栗子:

    @Test
    public void testGetAndAddInt() throws NoSuchFieldException, IllegalAccessException {
        final Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        Unsafe unsafe = (Unsafe) theUnsafe.get(null);
    
        long offset = unsafe.objectFieldOffset(Student.class.getDeclaredField("age"));
    
        unsafe.putInt(Student.class,offset,30);
        System.out.println(unsafe.getAndAddInt(Student.class,offset,3));
        System.out.println(unsafe.getInt(Student.class,offset));
    }
    

    3.13从一个变量获取值并设置成另一个值

    public final int getAndSetInt(Object o, long offset, int newValue) {
        return theInternalUnsafe.getAndSetInt(o, offset, newValue);
    }
    
    public final long getAndSetLong(Object o, long offset, long newValue) {
        return theInternalUnsafe.getAndSetLong(o, offset, newValue);
    }
    
    public final Object getAndSetObject(Object o, long offset, Object newValue) {
        return theInternalUnsafe.getAndSetObject(o, offset, newValue);
    }
    

    栗子:

    public void testGetAndSetInt() throws NoSuchFieldException, IllegalAccessException {
        final Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        Unsafe unsafe = (Unsafe) theUnsafe.get(null);
    
        long offset = unsafe.objectFieldOffset(Student.class.getDeclaredField("age"));
    
        unsafe.putInt(Student.class,offset,30);
        System.out.println(unsafe.getAndSetInt(Student.class,offset,32));
        System.out.println(unsafe.getInt(Student.class,offset));
    }
    

    4.Unsafe底层实现

    既然Unsafe能够操作内存,那么它的底层一定需要JVM的支持,在OpenJDK的HotSpot虚拟机源码中有Unsafe类的JVM层面的实现,对应于prims目录中的Unsafe.cpp,java11的jvm源码关于Unsafe的实现还多了Unsafe.hpp这个头文件。Unsafe.cpp这个文件小两千行代码,且注释比较少,要想全搞明白需要Debug很久,这里我就不具体分析了,以后写JVM源码时再具体分析这个类。如果感兴趣的朋友可以下载OpenJDK源码,编译后搭建调试环境,关于JVM源码编译,大家可以参考我的这篇文章:Ubuntu下编译openjdk8

    Ubuntu下编译openjdk11

    调试环境的搭建,大家可以参考我的这篇文章:JVM源码调试环境搭建

    OpenJDK大家可以到官网下载,也可以到我的github下载:

    相关文章

      网友评论

        本文标题:Unsafe类源码解析

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