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可以绕过构造函数完成对象的创建
- 定义一个类
public class Student {
private int age;
public Student() {
this.age = 18;
}
public int getAge() {
return age;
}
}
- 测试使用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还可以实现对类的加载
- 定义一个类:
package study.wyy.juc.atom;
public class A {
private int i;
public A() {
this.i = 10;
}
public int getI() {
return i;
}
}
-
javac 编译这个java文件,将其放到一个目录下,注意创建对应的报名目录
-
使用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());
}
}