美文网首页
Unsafe 类

Unsafe 类

作者: lj72808up | 来源:发表于2021-05-08 17:30 被阅读0次
  1. Unsafe类 不能直接 new, 其构造函数被私有化
  2. public final class Unsafe : Unsafe 是 final 的, 不能被继承
  3. 方法:
    • native 的 compareAndSwap 方法, 实现 CAS 比较交换
    • native 的 put/get volatile 类型变量
    • native 的 park/unpark ,实现加锁解锁
    • java 本地方法的 getAndSet 和 getAndAdd, 内部调用了 native 的 compareAndSwap
  1. Unsafe 之所以叫做不安全的, 是因为他可以直接操作内存地址, 修改对象属性

  2. Unsafe 暴露了一个静态方法用于获取对象, 但会校验加载 Unsafe 的是否为 Bootstrap ClassLoader, 不是则抛异常

    public static Unsafe getUnsafe() {
        Class var0 = Reflection.getCallerClass();
        if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
            throw new SecurityException("Unsafe");
        } else {
            return this.theUnsafe;
        }
    }
    
    // 静态代码块
    static {
        registerNatives();
        Reflection.registerMethodsToFilter(Unsafe.class, new String[]{"getUnsafe"});
        theUnsafe = new Unsafe();
        ARRAY_BOOLEAN_BASE_OFFSET = theUnsafe.arrayBaseOffset(boolean[].class);
        ARRAY_BYTE_BASE_OFFSET = theUnsafe.arrayBaseOffset(byte[].class);
            ... ... ...
        ADDRESS_SIZE = theUnsafe.addressSize();
    }
    

因此, 如果想获取 Unsafe 实例, 只能通过反射. 看到 getUnsafe 返回 theUnsafe 属性, 且改属性在静态代码块中已被初始化. 即只要该类加载, 该类的实例就被存在属性中, 我们直接反射获取属性即可

public static Unsafe getUnsafe() throws IllegalAccessException, NoSuchFieldException {
        Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
        //Field unsafeField = Unsafe.class.getDeclaredFields()[0]; //也可以这样,作用相同
        unsafeField.setAccessible(true);
        Unsafe unsafe =(Unsafe) unsafeField.get(null);
        return unsafe;
    }

二. 功能实现

  1. 内存操作
    实现像 C++ 一样的申请, 释放, 调整, 拷贝内存. 通过 Unsafe 申请的内存是对外内存, 需要手动释放. 方法为在 finally 中释放申请的内存

    • 方法列表
    // 分配新的本地空间
    public native long allocateMemory(long bytes);
    // 重新调整内存空间的大小
    public native long reallocateMemory(long address, long bytes);
    // 将内存设置为指定值
    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 native void freeMemory(long address);
    
    • 示例
    private static void memoryTest(Unsafe unsafe) {
        int size = 4;
        // 分配4字节长度的内存
        long addr = unsafe.allocateMemory(size);
        // 分配一个8字节长度的内存
        long addr3 = unsafe.reallocateMemory(addr, size * 2);
        System.out.println("addr地址: "+addr);
        System.out.println("addr3地址: "+addr3);
        try {
            // 初始化内存, 向4个字节中的每个字节, 一次写入 byte(1). 即内存中为  0001 0001 0001 0001 => 组成的4字节 int 型值为 16843009
            unsafe.setMemory(null,addr ,size,(byte)1);
            // 从 addr 想 addr3 拷贝值, 因为 addr3 比 addr 长1倍, 所以需要拷贝2次
            // 拷贝后, addr3 中的内容为  0001 0001 0001 0001 0001 0001 0001 0001 => long 型值为 72340172838076673
            for (int i = 0; i < 2; i++) {
                unsafe.copyMemory(null,addr,null,addr3+size*i,4);
            }
            System.out.println(unsafe.getInt(addr));
            System.out.println(unsafe.getLong(addr3));
        }finally {
            // 手动释放内存, Unsafe的内存不在 jvm 中
            unsafe.freeMemory(addr);
            unsafe.freeMemory(addr3);
        }
    }
    
  2. 内存屏障

    • Unsafe 提供了防止指令重排的方法. 分为读重排, 写重排, 读写重排. 内存屏障相当于一个操作的同步点, 确保同步点之前的指令不会被重排到同步点之后
     public native void loadFence();
     public native void storeFence();
     public native void fullFence();
    
    • loadFence 为例, 他会禁止读操作重排序, 确保该点之前的所有读操作都已完成, 且会将线程从主内存拷贝过来的缓存值设为无效, 重新从主内存中加载数据
    • 示例, 我们用 Unsafe 的读屏障, 模拟 volatile
    // 定义一个线程方法,在线程中去修改flag标志位,注意这里的flag是没有被volatile修饰的:
    class ChangeThread implements Runnable{
     /**volatile**/ boolean flag=false;
     @Override
     public void run() {
         try {
             Thread.sleep(3000);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }        
         System.out.println("subThread change flag to:" + flag);
         flag = true;
     }
    }
    
    // 主线程用读屏障, 确保变量的可见性  
    public static void main(String[] args){
     ChangeThread changeThread = new ChangeThread();
     new Thread(changeThread).start();
     while (true) {
         boolean flag = changeThread.isFlag();
         unsafe.loadFence(); //加入读内存屏障
         if (flag){
             System.out.println("detected flag changed");
             break;
         }
     }
     System.out.println("main thread end");
    }
    
  3. 对象操作

    1. 修改属性值

      • Unsafe 可以通过计算某个属性在对象内中的偏移量, 直接修改内存地址上的值, 达到修改对象属性的目的
          /**
       * 获取属性的内存地址修改对象属性值
       */
      public static void fieldTest(Unsafe unsafe) throws NoSuchFieldException {
          User user=new User();
          long fieldOffset = unsafe.objectFieldOffset(User.class.getDeclaredField("age"));
          System.out.println("内存地址offset:"+fieldOffset);
          unsafe.putInt(user,fieldOffset,20);
          System.out.println("age:"+unsafe.getInt(user,fieldOffset));
          System.out.println("age:"+user.getAge());
      }
      
      • Unsafe 的操作属性修改有三类: volatile读写, 有序写入, 普通读写
      // 在对象的指定偏移地址获取一个对象引用
      // (1) 普通读写
      public native Object getObject(Object o, long offset);
      // 在对象指定偏移地址写入一个对象引用
      public native void putObject(Object o, long offset, Object x);
      
      // (2) volatile 读写
      //在对象的指定偏移地址处读取一个int值,支持volatile load语义
      public native int getIntVolatile(Object o, long offset);
      //在对象指定偏移地址处写入一个int,支持volatile store语义
      public native void putIntVolatile(Object o, long offset, int x);
      
      // (3) 顺序读写
      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);
      
      • 从实现效果上看
        • 有序写入: 保证写入时的有序性,而不保证可见性,也就是一个线程写入的值不能保证其他线程立即可见。
        • volatile写入: 保证可见性和有序性。get 和 set 会同步操作主存. 在执行get操作时,强制从主存中获取属性值; 在使用put方法设置属性值时,会强制将值更新到主存中,从而保证这些变更对其他线程是可见的。
    2. 初始化对象

      • Unsafe 的 allocateInstance 方法,允许我们使用非常规的方式进行对象的实例化. 不通过构造函数初始化对象, 通过对象及其属性的字段长度初始化
      • Unsafe 的 allocateInstance 初始化对象, 只能将属性设置为每种数据类型的默认值
      public class A {
          private int b;
          public A(){
              this.b =1;
          }
      }
      
      public void objTest() throws Exception{
          A a1=new A();
          System.out.println(a1.getB());
          A a2 = A.class.newInstance();
          System.out.println(a2.getB());
          A a3= (A) unsafe.allocateInstance(A.class);
          System.out.println(a3.getB());
      }
      
  4. 数组操作 (略)

  5. CAS
    在 Unsafe 类中,提供了 compareAndSwapObjectcompareAndSwapIntcompareAndSwapLong 方法来实现的对 Object、int、long 类型的CAS操作

    public final native boolean compareAndSwapInt(Object o, long offset,int expected,int x);
    
    • compareAndSwapInt 方法名: 表示 CAS 更新对象中的 int 属性
    • Object o : 待更新的对象
    • long offset : 待更新字段的地址偏移量
    • int expected,int x : 表示只有字段值为 expected 时, 才把字段值改为 x

    如下示例 , 输出 1 2 3 4 5 6 7 8 9

    public class Test {
        private volatile int a;
        public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
            Test Test=new Test();
            Unsafe unsafe = Test.getUnsafe();
            // 负责 [1,5) 的累加
            new Thread(()->{
                for (int i = 1; i < 5; i++) {
                    Test.increment(i,unsafe);
                    System.out.print(Test.a+" ");
                }
            }).start();
            // 负责 [5,10] 的累加
            new Thread(()->{
                for (int i = 5 ; i <10 ; i++) {
                    Test.increment(i,unsafe);
                    System.out.print(Test.a+" ");
                }
            }).start();
        }
    
        private void increment(int x, Unsafe unsafe){
            while (true){
                try {
                    // 只有在a的值等于传入的参数x减一时,才会将a的值变为x
                    long fieldOffset = unsafe.objectFieldOffset(Test.class.getDeclaredField("a"));
                    if (unsafe.compareAndSwapInt(this,fieldOffset,x-1,x))
                        break;
                    else
                        Thread.yield();
                } catch (NoSuchFieldException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
  6. 线程调度
    AQS 经常使用 LockSupport 挂起或唤醒指定线程. LockSupport 的 park 方法调用了Unsafe 的 park 方法来阻塞当前线程,此方法将线程阻塞后就不会继续往后执行,直到有其他线程调用 unpark 方法唤醒当前线程。

    • unpark 方法是其他线程调用, 唤醒本线程的
    • park 方法是本线程调用, 自己阻塞自己

    下面一个例子对Unsafe的这两个方法进行测试:

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        Thread mainThread = Thread.currentThread();
        Unsafe unsafe = Test.getUnsafe();
        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(5);
                System.out.println("subThread try to unpark mainThread");
                // 新线程唤醒主线程
                unsafe.unpark(mainThread);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    
        System.out.println("park main mainThread");
        // 主线程自己阻塞自己
        unsafe.park(false,0L);
        System.out.println("unpark mainThread success");
    }
    
  7. 对 Class 操作
    Unsafe 对 Class 的相关操作主要包括类加载和静态变量的操作方法。

    1. 加载静态变量
      • 前面提到 unsafe 可以操作对象的属性, 但静态属性和对象没关系, 和类有关系; 静态属性单独存在方法区, jdk1.8 后静态属性存在堆中, 但仍与普通对象在不同区域. 因此操作静态属性的是新的 api
      //获取静态属性的偏移量
      public native long staticFieldOffset(Field f);
      //获取静态属性的对象指针
      public native Object staticFieldBase(Field f);
      //判断类是否需要实例化(用于获取类的静态属性前进行检测)
      // 如果 jvm 已加载 Class c , 则返回 false, 否则返回 true
      public native boolean shouldBeInitialized(Class<?> c);
      
    • 测试示例
       private static void staticTest(Unsafe unsafe) throws Exception {
         User user = new User(); // 为了让 jvm 加载类, 从而初始化静态字段. 下面那句和这句目的一样
         //        Class.forName(User.class.getName());
         System.out.println(unsafe.shouldBeInitialized(User.class));
         // 反射字段
         Field sexField = User.class.getDeclaredField("name");
         // 获取静态字段 "name" 对象
         Object fieldBase = unsafe.staticFieldBase(sexField);
         // 获取静态字段 "name" 所在方法区/堆 中的地址
         long fieldOffset = unsafe.staticFieldOffset(sexField);
         Object object = unsafe.getObject(fieldBase, fieldOffset);
         System.out.println(object);
      }
      
      class User {
       public static String name = "Hydra";
       int age;
      }
      
      上面示输出 false, Hydra
      如果注释掉User user = new User(); , 输出 true , null
    1. 加载类
    public native Class<?> defineClass(String name, byte[] b, int off, int len,
                                   ClassLoader loader,ProtectionDomain protectionDomain);
    
  8. 系统信息
    Unsafe 中提供的 addressSizepageSize 方法用于获取系统信息

    • addressSize方法会返回系统指针的大小,如果在64位系统下默认会返回8,而32位系统则会返回4。
    • pageSize 方法会返回内存页的大小,值为2的整数幂。
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        Unsafe unsafe = Test.getUnsafe();
        System.out.println(unsafe.addressSize());  // 8 (64位系统, 地址长度为8字节; 32位系统, 地址长度为4字节)
        System.out.println(unsafe.pageSize());     // 4 (内存页大小: 4kb) 
    }
    

相关文章

  • sun.misc.Unsafe类 (内存操作/对象字段操作/原子

    一. 关于sun.misc.Unsafe类 sun.misc.Unsafe类的描述如下: 简单来说, Unsafe...

  • 说说Java的Unsafe类

    本文主要内容 Unsafe类介绍 Unsafe的主要功能 总结 1、Unsafe类介绍 第一次看到这个类时被它的名...

  • Unsafe类学习笔记

    Unsafe类学习笔记 Unsafe 类初识 Unsafe位于sun.misc包内,看其命名就知道和注重安全性的j...

  • Netty源码学习(6)--pipeline学习2

    Unsafe unsafe是不安全的意思,不要在应用程序里面直接使用Unsafe以及他的衍生类对象。Unsafe ...

  • Unsafe类

      这两天看netty源码看到一些UnpooledUnsafeHeapByteBuf、UnpooledUnsafe...

  • Unsafe类

    Unsafe类是Java不对外提供的不安全的类,juc包中的atomic*类都是用这个类实现的 // Uns...

  • UnSafe 类

    https://tech.meituan.com/2019/02/14/talk-about-java-magic...

  • Unsafe 类

    Unsafe类 不能直接 new, 其构造函数被私有化 public final class Unsafe : U...

  • UnSafe类中的一些重要方法

    UnSafe类中的一些重要方法 JDK中的rt.jar保重Unsafe类中提供了硬件级别的原子性操作,Unsafe...

  • CAS

    Cas依赖于Unsafe类中的cpmpareAndSwapInt方法实现原子操作Unsafe是cas的核心类,ja...

网友评论

      本文标题:Unsafe 类

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