美文网首页
sun.misc.Unsafe对象学习

sun.misc.Unsafe对象学习

作者: jecyhw | 来源:发表于2019-04-30 17:37 被阅读0次

    Unsafe类的作用

    Unsafe类提供了硬件级别的原子操作,Java无法直接访问到操作系统底层(如系统硬件等),为此Java使用native方法来扩展Java程序的功能。Java并发包(java.util.concurrent)中大量使用了Unsafe类提供的CAS方法。Unsafe类还提供了阻塞和唤醒线程的方法,以及volatile read/write操作等。

    获取Unsafe对象

    public final class Unsafe {
      private static final Unsafe theUnsafe;
      // ...
      private Unsafe() {
      }
    
      @CallerSensitive
      public static Unsafe getUnsafe() {
        Class var0 = Reflection.getCallerClass();
        if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
          throw new SecurityException("Unsafe");
        } else {
          return theUnsafe;
        }
      }
      // ...
    }
    
    1. Unsafe被设计成单例模式,构造方法是私有的,不能直接通过new Unsafe();

    2. 不能通过调用Unsafe.getUnsafe()获取,因为getUnsafe被设计成只能从引导类加载器(bootstrap class loader)加载

      getClassLoader链接:https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html#getClassLoader--

    正确获取Unsafe的姿势

    package com.sankuai.meituan;
    
    import sun.misc.Unsafe;
    import java.lang.reflect.Field;
    
    public class UnsafeWrapper {
        private static Unsafe unsafe;
    
        public static Unsafe getUnsafe() {
            if (unsafe == null) {
                try {
                    // 通过反射得到unsafe对应的Field对象
                    Field field = Unsafe.class.getDeclaredField("theUnsafe");
                    // 设置该Field为可访问
                    field.setAccessible(true);
                    // 通过Field得到该Field对应的具体对象,传入null是因为该Field为static的
                    unsafe = (Unsafe) field.get(null);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return unsafe;
        }
    }
    
    

    Unsafe类中的API

    1.内存管理,直接操作内存的方法

    //分配指定大小的内存
    public native long allocateMemory(long bytes);
    //重新分配 (扩展) 一块内存 (之前分配内存当然会被GC回收掉). 第一个参数为原内存地址,返回新的内存地址. 原内存中的内容会迁移到新开辟的内存中.
    public native long reallocateMemory(long address, long bytes);
    //用于释放allocateMemory和reallocateMemory申请的内存
    public native void freeMemory(long address);
    //设置给定内存地址的值
    public native void putAddress(long address, long x);
    //获取指定内存地址的值
    public native long getAddress(long address);
    
    //设置指定内存的byte值
    public native byte  getByte(long address);
    //设置指定内存的byte值
    public native void  putByte(long address, byte x);
    //其他基本数据类型(long,char,float,double,short等)的操作与putByte及getByte相同
    
    
    package com.sankuai.meituan;
    
    import lombok.extern.slf4j.Slf4j;
    import org.junit.Test;
    import sun.misc.Unsafe;
    
    @Slf4j
    public class UnsafeMemoryTest {
        @Test
        public void test() {
            Unsafe unsafe = UnsafeWrapper.getUnsafe();
            int size = 16;
            int data = 1024;
            long memoryAddress = unsafe.allocateMemory(size);
            //直接往内存写入数据
            unsafe.putAddress(memoryAddress, data);
            //获取指定内存地址的数据
            log.info("getAddress:{}, data:{}", unsafe.getAddress(memoryAddress), data);
    
            //putLong和putAddress效果一样
            data = 2 * data;
            unsafe.putLong(memoryAddress, data);
            unsafe.putAddress(memoryAddress + 8, data * 2);
            unsafe.putAddress(memoryAddress + 16, data * 4);
            unsafe.putAddress(memoryAddress + 24, data * 8);
            log.info("getAddress:{}, data:{}", unsafe.getAddress(memoryAddress), data);
    
            //调换顺序报错
            long reallocateMemoryAddress = unsafe.reallocateMemory(memoryAddress, size * 1024);
            log.info("memoryAddress:{}, reallocateMemoryAddress:{}", memoryAddress, reallocateMemoryAddress);
    
            log.info("getAddress:{}, data:{}", unsafe.getAddress(reallocateMemoryAddress), data);
            log.info("getAddress+8:{}, data:{}", unsafe.getAddress(reallocateMemoryAddress + 8), data * 2);
            log.info("getAddress+16:{}, data:{}", unsafe.getAddress(reallocateMemoryAddress + 16), data * 4);
            log.info("getAddress+24:{}, data:{}", unsafe.getAddress(reallocateMemoryAddress + 24), data * 8);
            log.info("getAddress+32:{}, data:{}", unsafe.getAddress(reallocateMemoryAddress + 32), data * 16);
    
            unsafe.freeMemory(reallocateMemoryAddress);
        }
    }
    
    

    在不同的jdk版本上allocateMemory申请空间的时候会在头尾加上一点缓冲区,比如这里的size是24,分配的是32(最小大小是16,就是size小于等于16,分配的是16)。

    2.动态类加载操作

    //告诉JVM定义一个类,返回类实例,此方法会跳过JVM的所有安全检查。  
    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);
    //判断是否需要加载一个类
    public native boolean shouldBeInitialized(Class<?> c);
    //确保类一定被加载 
    public native  void ensureClassInitialized(Class<?> c)
    
    package com.sankuai.meituan;
    
    import org.junit.Test;
    
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    public class UnsafeDefineClassTest {
        @Test
        public void test() throws IOException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
                File f = new File("/Users/jec/IdeaProjects/UnsafeLearning/src/test/java/com/sankuai/meituan/Hello.class");
                FileInputStream input = new FileInputStream(f);
                byte[] content = new byte[(int) f.length()];
                input.read(content);
                input.close();
    
                Class c = UnsafeWrapper.getUnsafe().defineClass(null, content, 0, content.length, null, null);
                Method m = c.getMethod("hello");
                m.invoke(c.newInstance(), null);
        }
    }
    

    3.非常规实例化对象

    //传入一个对象的class并创建该实例对象,但不会调用构造方法
    public native Object allocateInstance(Class cls) throws InstantiationException;
    
    package com.sankuai.meituan;
    
    import lombok.extern.slf4j.Slf4j;
    import org.junit.Test;
    import sun.misc.Unsafe;
    
    @Slf4j
    public class UnsafeAllocateInstanceTest {
        @Test
        public void test() throws InstantiationException {
            Unsafe unsafe = UnsafeWrapper.getUnsafe();
            Rectangle rectangle = (Rectangle) unsafe.allocateInstance(Rectangle.class);
            log.info("func=unsafeAllocateInstanceTest rectangle:{}", rectangle);
        }
    }
    

    4.实例对象字段以及静态属性的操作

    //获取字段f在实例对象中的偏移量
    public native long objectFieldOffset(Field f);
    //静态属性的偏移量,用于在对应的Class对象中读写静态属性
    public native long staticFieldOffset(Field f);
    //返回值就是f.getDeclaringClass()
    public native Object staticFieldBase(Field f);
    
    //获得给定对象偏移量上的int值,所谓的偏移量可以简单理解为指针指向该变量的内存地址,
    //通过偏移量便可得到该对象的变量,进行各种操作
    public native int getInt(Object o, long offset);
    //设置给定对象上偏移量的int值
    public native void putInt(Object o, long offset, int x);
    //设置给定对象的int值,使用volatile语义,即设置后立马更新到内存对其他线程可见
    public native void  putIntVolatile(Object o, long offset, int x);
    //获得给定对象的指定偏移量offset的int值,使用volatile语义,能获取到最新的int值。
    public native int getIntVolatile(Object o, long offset);
    //这是一个有延迟的方法,使用这个方法写入volatile对象只能够避免写重排序,但使volatile失去可见性,不保证值的改变被其他线程立即看到,这样的技巧可以在某些场景下提高效率。
    public native void putOrderedInt(Object o,long offset,int x);
    
    //其他基本数据类型(long,char,byte,float,double)的操作与getInt、putInt、putIntVolatile、getIntVolatile、putOrderedInt相同,引用类型Object也一样。
    
    //将指定对象的给定offset偏移量内存块中的所有字节设置为固定值
    public native void setMemory(Object o, long offset, long bytes, byte value);
    //将给定offset偏移量内存块中的所有字节bytes设置为固定值value
    public void setMemory(long offset, long bytes, byte value);
    //从srcObj对象的srcOffset位置开始复制bytes个字节到destObj(从destOffset开始)
    public native void copyMemory(Object srcObj, long srcOffset, Object destObj, long destOffset, long bytes);
    //从srcOffset复制bytes个字节到destOffset开始的内存块
    public void copyMemory(long srcOffset, long destOffset, long bytes)
    
    package com.sankuai.meituan;
    
    import lombok.extern.slf4j.Slf4j;
    import org.junit.Test;
    import sun.misc.Unsafe;
    
    @Slf4j
    public class UnsafeObjectTest {
        @Test
        public void test() throws NoSuchFieldException {
            Unsafe unsafe = UnsafeWrapper.getUnsafe();
            int h = 5, w = 6;
            Rectangle rectangle = new Rectangle();
    
            //获取对象字段的偏移位置
            long hObjectFieldOffset = unsafe.objectFieldOffset(Rectangle.class.getDeclaredField("h"));
            long wObjectFieldOffset = unsafe.objectFieldOffset(Rectangle.class.getDeclaredField("w"));
            log.info("hObjectFieldOffset:{}, wObjectFieldOffset:{}", hObjectFieldOffset, wObjectFieldOffset);
            //设置对象上的字段值
            unsafe.putOrderedInt(rectangle, hObjectFieldOffset, h);
            unsafe.putInt(rectangle, wObjectFieldOffset, w);
            //获取对象上的字段值
            log.info("get rectangle.h:{}, h:{}", unsafe.getInt(rectangle, hObjectFieldOffset), h);
            log.info("get rectangle.w:{}, h:{}", unsafe.getInt(rectangle, wObjectFieldOffset), w);
    
            Rectangle subRectangle = new Rectangle(h, w, null);
    
            long subRectangleObjectFieldOffset = unsafe.objectFieldOffset(Rectangle.class.getDeclaredField("subRectangle"));
    
            log.info("subRectangleObjectFieldOffset:{}", subRectangleObjectFieldOffset);//设置对象上的字段值
            unsafe.putObject(rectangle, subRectangleObjectFieldOffset, subRectangle);
            //获取对象上的字段值
            log.info("get subRectangle:{}", unsafe.getObject(rectangle, subRectangleObjectFieldOffset));
    
            int count = 4;
            //获取静态字段的偏移位置
            log.info("staticFieldBase count:{} ", unsafe.staticFieldBase(Rectangle.class.getDeclaredField("count")));
            long staticFieldOffset = unsafe.staticFieldOffset(Rectangle.class.getDeclaredField("o"));
            log.info("staticFieldOffset:{}", staticFieldOffset);
            //设置类静态字段得值
            unsafe.putInt(Rectangle.class, staticFieldOffset, count);
            //获取类静态字段得值
            log.info("get Rectangle.count:{}, count:{}", unsafe.getInt(Rectangle.class, staticFieldOffset), count);
    
            unsafe.setMemory(rectangle, hObjectFieldOffset, 8, (byte)0);
            log.info("get rectangle.h:{}, get rectangle.w:{}", unsafe.getInt(rectangle, hObjectFieldOffset), unsafe.getInt(rectangle, wObjectFieldOffset));
        }
    }
    
    

    5.数组操作

    //获取数组第一个元素的偏移地址
    public native int arrayBaseOffset(Class arrayClass);
    //数组中一个元素占据的内存空间,arrayBaseOffset与arrayIndexScale配合使用,可定位数组中每个元素在内存中的位置
    public native int arrayIndexScale(Class arrayClass);
    
    
    package com.sankuai.meituan;
    
    import lombok.extern.slf4j.Slf4j;
    import org.junit.Test;
    import sun.misc.Unsafe;
    
    @Slf4j
    public class UnsafeArrayTest {
        @Test
        public void test() {
            Unsafe unsafe = UnsafeWrapper.getUnsafe();
    
            Rectangle rectangle = new Rectangle(5, 6, null);
            Object[] array = new Object[] {rectangle, rectangle, rectangle};
    
            //获取数组第一个元素的偏移地址
            long baseOffset = unsafe.arrayBaseOffset(Object[].class);
            //获取JVM的地址空间大小
            int addressSize = unsafe.addressSize();
            //获取对象地址
            long objectAddress;
            switch (addressSize)
            {
                case 4://32位jvm
                    objectAddress = unsafe.getInt(array, baseOffset);
                    break;
                case 8://64位jvm
                    objectAddress = unsafe.getLong(array, baseOffset);
                    break;
                default:
                    throw new Error("unsupported address size: " + addressSize);
            }
            log.info("array baseOffset:{}, addressSize:{}, objectAddress:{}", baseOffset, addressSize, objectAddress);
    
            log.info("");
            log.info("---------------------------");
    
            log.info("boolean[].class arrayBaseOffset:{}", unsafe.arrayBaseOffset(boolean[].class));
            log.info("byte[].class arrayBaseOffset:{}", unsafe.arrayBaseOffset(byte[].class));
            log.info("short[].class arrayBaseOffset:{}", unsafe.arrayBaseOffset(short[].class));
            log.info("char[].class arrayBaseOffset:{}", unsafe.arrayBaseOffset(char[].class));
            log.info("int[].class arrayBaseOffset:{}", unsafe.arrayBaseOffset(int[].class));
            log.info("long[].class arrayBaseOffset:{}", unsafe.arrayBaseOffset(long[].class));
            log.info("float[].class arrayBaseOffset:{}", unsafe.arrayBaseOffset(float[].class));
            log.info("double[].class arrayBaseOffset:{}", unsafe.arrayBaseOffset(double[].class));
            log.info("Object[].class arrayBaseOffset:{}", unsafe.arrayBaseOffset(Object[].class));
            log.info("boolean[].class arrayIndexScale:{}", unsafe.arrayIndexScale(boolean[].class));
            log.info("byte[].class arrayIndexScale:{}", unsafe.arrayIndexScale(byte[].class));
            log.info("short[].class arrayIndexScale:{}", unsafe.arrayIndexScale(short[].class));
            log.info("char[].class arrayIndexScale:{}", unsafe.arrayIndexScale(char[].class));
            log.info("int[].class arrayIndexScale:{}", unsafe.arrayIndexScale(int[].class));
            log.info("long[].class arrayIndexScale:{}", unsafe.arrayIndexScale(long[].class));
            log.info("float[].class arrayIndexScale:{}", unsafe.arrayIndexScale(float[].class));
            log.info("double[].class arrayIndexScale:{}", unsafe.arrayIndexScale(double[].class));
            log.info("Object[].class arrayIndexScale:{}", unsafe.arrayIndexScale(Object[].class));
        }
    }
    

    6.多线程同步

    //获取持有锁,已不建议使用
    @Deprecated
    public native void monitorEnter(Object o);
    //释放锁,已不建议使用
    @Deprecated
    public native void monitorExit(Object o);
    //尝试获取锁,已不建议使用
    @Deprecated
    public native boolean tryMonitorEnter(Object o);
    

    7.CAS 操作相关

    //第一个参数o为给定对象,offset为对象内存的偏移量,通过这个偏移量迅速定位字段并设置或获取该字段的值,
    //expected表示期望值,x表示要设置的值,下面3个方法都通过CAS原子指令执行操作。
    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);
    //以及jdk1.8基于cas操作新增扩展出来的getAndAddInt、getAndAddLong、getAndSetInt、getAndSetLong、getAndSetObject
    public final int getAndAddInt(Object o, long offset, int delta)
    public final long getAndAddLong(Object o, long offset, long delta)
    public final int getAndSetInt(Object o, long offset, int newValue)
    public final long getAndSetLong(Object o, long offset, long newValue)
    public final Object getAndSetObject(Object o, long offset, Object newValue)  
    

    8.挂起与恢复

    将一个线程进行挂起是通过park方法实现的,调用 park后,线程将一直阻塞直到超时或者中断等条件出现。unpark可以终止一个挂起的线程,使其恢复正常。Java对线程的挂起操作被封装在 LockSupport类中,LockSupport类中有各种版本pack方法,其底层实现最终还是使用Unsafe.park()方法和Unsafe.unpark()方法

    //线程调用该方法,线程将一直阻塞直到超时,或者是中断条件出现。  
    public native void park(boolean isAbsolute, long time);  
    
    //终止挂起的线程,恢复正常.java.util.concurrent包中挂起操作都是在LockSupport类实现的,其底层正是使用这两个方法,  
    public native void unpark(Object thread); 
    

    9.内存屏障

    这里主要包括了loadFence、storeFence、fullFence等方法,这些方法是在**java **8新引入的,用于定义内存屏障,避免代码重排序,与Java内存模型相关。

    //在该方法之前的所有读操作,一定在load屏障之前执行完成
    public native void loadFence();
    //在该方法之前的所有写操作,一定在store屏障之前执行完成
    public native void storeFence();
    //在该方法之前的所有读写操作,一定在full屏障之前执行完成,这个内存屏障相当于上面两个的合体功能
    public native void fullFence();
    

    10.包装受检异常为运行时异常

    public native void throwException(Throwable var1);
    

    11.Info 仅仅是返回一个低级别的内存相关的信息

    //获取JVM的地址空间大小,4或者8字节
    public native int addressSize();
    //获取本机内存的页数,一般是4096字节
    public native int pageSize();  
    

    12.看系统平均负载

    //获取系统的平均负载值,loadavg这个double数组将会存放负载值的结果,nelems决定样本数量,nelems只能取值为1到3,
    //分别代表最近1、5、15分钟内系统的平均负载。如果无法获取系统的负载,此方法返回-1,否则返回获取到的样本数量(loadavg中有效的元素个数)。
    public native int getLoadAverage(double[] loadavg, int nelems);
    
    package com.sankuai.meituan;
    
    import lombok.extern.slf4j.Slf4j;
    import org.junit.Test;
    import sun.misc.Unsafe;
    
    @Slf4j
    public class UnsafeGetLoadAverageTest {
        @Test
        public void test() {
            Unsafe unsafe = UnsafeWrapper.getUnsafe();
            double[] loadAvg = new double[3];
            //获取在过去的1、5、15分钟CPU正在处理以及等待CPU处理的进程数之和的统计信息
            //也就是CPU使用队列的长度的统计信息
            unsafe.getLoadAverage(loadAvg, loadAvg.length);
            log.info("loadAvg:{}", loadAvg);
        }
    }
    

    参考资料

    http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/sun/misc/Unsafe.java

    https://leokongwq.github.io/2016/12/31/java-magic-unsafe.html

    https://juejin.im/entry/595c599e6fb9a06bc6042514

    http://www.importnew.com/8494.html

    系统平均负载(Load average)与CPU利用率: https://blog.csdn.net/zhangnn5/article/details/7048889

    其他

    jdk测试版本信息

    Rectangle类

    @Data
    @AllArgsConstructor
    @Slf4j
    private static class Rectangle {
        static int count = 0;
        private int h;
        private int w;
        private Rectangle subRectangle;
    
        public Rectangle() {
            log.info("Rectangle构造函数");
        }
    }
    

    相关文章

      网友评论

          本文标题:sun.misc.Unsafe对象学习

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