sun.misc.Unsafe介绍

juc包下的原子类型和并发容器都依赖了sun.misc.Unsafe这个类,该类是可以直接对内存进行相关操作的,甚至还可以通过汇编指令直接进行CPU的操作。

1 sun.misc.Unsafe介绍

sun.misc.Unsafe提供了非常多的底层操作方法,这些方法更加接近机器硬件(CPU/内存),因此效率会更高。不仅Java本身提供的很多API都对其有严重依赖,而且很多优秀的第三方库/框架都对它有着严重的依赖,比如LMAX Disruptor,不熟悉系统底层,不熟悉C/C++汇编等的开发者没有必要对它进行深究,但是这并不妨碍我们直接使用它。

但是,在使用的过程中,如果使用不得当,那么代价将是非常高昂的,你可以用,但请慎用!

2 如何获取Unsafe

原子类型都依赖于Unsafe,就可以参考原子类的源码,(以AtomicInteger源码为例),如下:

private static final Unsafe unsafe = Unsafe.getUnsafe();

通过调用静态方法Unsafe.getUnsafe()就可以获取一个Unsafe的实例,但是在我们自己的类中执行同样的代码却会抛出SecurityException异常。

public static void main(String[] args) {
    Unsafe unsafe = Unsafe.getUnsafe();
}
Exception in thread "main" java.lang.SecurityException: Unsafe

为什么在AtomicInteger中可以,在我们自己的代码中就不行呢?只能看一下getUnsafe方法的源码:

@CallerSensitive
 public static Unsafe getUnsafe() {
     Class<?> caller = Reflection.getCallerClass();
     // 如果对getUnsafe方法的调用类不是由系统类加载器加载的,则会抛出异常
     if (!VM.isSystemDomainLoader(caller.getClassLoader()))
         throw new SecurityException("Unsafe");
     return theUnsafe;
 }

如果调用该方法的类不是被系统类加载器加载的就会抛出异常,通常情况下开发者所开发的Java类都会被应用类加载器进行加载。

但是在Unsafe源码中,存在一个Unsafe的实例theUnsafe,该实例是类私有成员,并且在Unsafe类的静态代码块中已经被初始化了

public final class Unsafe {

    private static native void registerNatives();
    static {
        registerNatives();
        sun.reflect.Reflection.registerMethodsToFilter(Unsafe.class, "getUnsafe");
    }

    private Unsafe() {}

    private static final Unsafe theUnsafe = new Unsafe();
    ....
}

所以可以通过反射的方式尝试获取该成员的属性:

public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
    Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
    theUnsafe.setAccessible(true);
    /* 强转 */
    Unsafe unsafe = (Unsafe) theUnsafe.get(null);
}

在Unsafe类中,几乎所有的方法都是native(本地)方法,本地方法是由C/C++实现的,在Java中提供了使用C/C++代码的接口,该接口称为JNI(Java Native Interface)

3 Unsafe应用

Unsafe非常强大,它可以帮助我们获得某个变量的内存偏移量,获取内存地址,在其内部更是运行了汇编指令,为我们在高并发编程中提供Lock Free的解决方案,提高并发程序的执行效率。但是Unsafe正如它的名字一样是很不安全的,如果使用错误则会出现很多灾难性的问题(本地代码所属的内存并不在JVM的堆栈中)

3.1 绕过类构造函数完成对象创建

主动使用某个类会引起类的加载过程发生直到该类完成初始化,最典型的例子是当我们通过关键字new进行对象的创建时,对应的构造函数肯定会被执行,但是Unsafe可以绕过构造函数完成对象的创建

  1. 定义一个类
public class Student {

    private int age;

    public Student() {
        this.age = 18;
    }

    public int getAge() {
        return age;
    }
}
  1. 测试使用Unsafe
public static void main(String[] args) throws IllegalAccessException, InstantiationException {
   // new构造对象
   Student student = new Student();
   System.out.println(student.getAge()); // 18

   // 反射构造对象
   Student student1 = Student.class.newInstance();
   System.out.println(student1.getAge()); // 18

   // 使用Unsafe
   Unsafe unsafe = getUnsafe();
   Student student2 = (Student) unsafe.allocateInstance(Student.class);
   // 此时也构造了Student实例,但是不是通过构造,所以此时age为int的默认值0
   System.out.println(student2.getAge()); // 0
}

public static Unsafe getUnsafe() {
   Field theUnsafe = null;
   try {
       theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
       theUnsafe.setAccessible(true);
       /* 强转 */
       return (Unsafe) theUnsafe.get(null);
   } catch (NoSuchFieldException | IllegalAccessException e) {
       throw new RuntimeException("get Unsafe fail by " + e.getMessage());
   }
}

借助于Unsafe的allocateInstance方法获得了Example的实例,该操作并不会导致Student 构造函数的执行

3.2 直接修改内存数据

public static void main(String[] args) throws NoSuchFieldException {
    Student student = new Student();
    System.out.println(student.getAge()); // 18

    // 使用unsafe修改age的值
    Field age = Student.class.getDeclaredField("age");
    //  使用unsafe首先获得f的内存偏移量
    // 然后直接进行内存操作,将accessNo的值修改为20
    Unsafe unsafe = getUnsafe();
    unsafe.putInt(student,unsafe.objectFieldOffset(age),20);
    System.out.println(student.getAge()); //20 
}

public static Unsafe getUnsafe() {
    Field theUnsafe = null;
    try {
        theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        /* 强转 */
        return (Unsafe) theUnsafe.get(null);
    } catch (NoSuchFieldException | IllegalAccessException e) {
        throw new RuntimeException("get Unsafe fail by " + e.getMessage());
    }
}

3.3 类的加载

Unsafe还可以实现对类的加载

  1. 定义一个类:
package study.wyy.juc.atom;
public class A {
    private int i;

    public A() {
        this.i = 10;
    }

    public int getI() {
        return i;
    }
}
  1. javac 编译这个java文件,将其放到一个目录下,注意创建对应的报名目录

  2. 使用Unsafe加载

public static void main(String[] args) throws Exception {
    // 读取A.class文件内容
    byte[] classContents = getClassContent();
    // 调用defineClass方法完成对A的加载、
    Unsafe unsafe = getUnsafe();
    Class<?> aClass = unsafe.defineClass(null, classContents, 0, classContents.length, null, null);
    // 调用getI
    Object res = aClass.getMethod("getI").invoke(aClass.newInstance(), null);
    System.out.println(res); // 10
}
/**
 * 读取class文件
 * @return
 * @throws Exception
 */
private static byte[] getClassContent() throws Exception {
    // class文件位置:
    File f = new File("C:\\Users\\20116651\\MyClassLoader\\study\\wyy\\juc\\atom\\A.class");
    try (FileInputStream input = new FileInputStream(f)) {
        byte[] content = new byte[(int) f.length()];
        input.read(content);
        return content;
    }
}
public static Unsafe getUnsafe() {
    Field theUnsafe = null;
    try {
        theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        /* 强转 */
        return (Unsafe) theUnsafe.get(null);
    } catch (NoSuchFieldException | IllegalAccessException e) {
        throw new RuntimeException("get Unsafe fail by " + e.getMessage());
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值