Java双刃剑之Unsafe类详解

前一段时间在研究juc源码的时候,发现在很多工具类中都调用了一个Unsafe类中的方法,出于好奇就想要研究一下这个类到底有什么作用,于是先查阅了一些资料,一查不要紧,很多资料中对Unsafe的态度都是这样的画风:

Java双刃剑之Unsafe类详解

其实看到这些说法也没什么意外,毕竟Unsafe这个词直译过来就是“不安全的”,从名字里我们也大概能看来Java的开发者们对它有些不放心。但是作为一名极客,不能你说不安全我就不去研究了,毕竟只有了解一项技术的风险点,才能更好的避免出现这些问题嘛。

下面我们言归正传,先通过简单的介绍来对Unsafe类有一个大致的了解。Unsafe类是一个位于sun.misc包下的类,它提供了一些相对底层方法,能够让我们接触到一些更接近操作系统底层的资源,如系统的内存资源、cpu指令等。而通过这些方法,我们能够完成一些普通方法无法实现的功能,例如直接使用偏移地址操作对象、数组等等。但是在使用这些方法提供的便利的同时,也存在一些潜在的安全因素,例如对内存的错误操作可能会引起内存泄漏,严重时甚至可能引起jvm崩溃。因此在使用Unsafe前,我们必须要了解它的工作原理与各方法的应用场景,并且在此基础上仍需要非常谨慎的操作,下面我们正式开始对Unsafe的学习。

Unsafe 基础

首先我们来尝试获取一个Unsafe实例,如果按照new的方式去创建对象,不好意思,编译器会报错提示你:

Unsafe() has private access in 'sun.misc.Unsafe'

查看Unsafe类的源码,可以看到它被final修饰不允许被继承,并且构造函数为private类型,即不允许我们手动调用构造方法进行实例化,只有在static静态代码块中,以单例的方式初始化了一个Unsafe对象:

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

在Unsafe类中,提供了一个静态方法getUnsafe,看上去貌似可以用它来获取Unsafe实例:

@CallerSensitive public static Unsafe getUnsafe() { Class var0 = Reflection.getCallerClass(); if (!VM.isSystemDomainLoader(var0.getClassLoader())) { throw new SecurityException("Unsafe"); } else { return theUnsafe; } }

但是如果我们直接调用这个静态方法,会抛出异常:

Exception in thread "main" java.lang.SecurityException: Unsafe at sun.misc.Unsafe.getUnsafe(Unsafe.java:90) at com.cn.test.GetUnsafeTest.main(GetUnsafeTest.java:12)

这是因为在getUnsafe方法中,会对调用者的classLoader进行检查,判断当前类是否由Bootstrap classLoader加载,如果不是的话那么就会抛出一个SecurityException异常。也就是说,只有启动类加载器加载的类才能够调用Unsafe类中的方法,来防止这些方法在不可信的代码中被调用。

那么,为什么要对Unsafe类进行这么谨慎的使用限制呢,说到底,还是因为它实现的功能过于底层,例如直接进行内存操作、绕过jvm的安全检查创建对象等等,概括的来说,Unsafe类实现功能可以被分为下面8类:

Java双刃剑之Unsafe类详解

创建实例

看到上面的这些功能,你是不是已经有些迫不及待想要试一试了。那么如果我们执意想要在自己的代码中调用Unsafe类的方法,应该怎么获取一个它的实例对象呢,答案是利用反射获得Unsafe类中已经实例化完成的单例对象:

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

在获取到Unsafe的实例对象后,我们就可以使用它为所欲为了,先来尝试使用它对一个对象的属性进行读写:

public 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类的objectFieldOffset方法获取了对象中字段的偏移地址,这个偏移地址不是内存中的绝对地址而是一个相对地址,之后再通过这个偏移地址对int类型字段的属性值进行了读写操作,通过结果也可以看到Unsafe的方法和类中的get方法获取到的值是相同的。

offset:12 age:20 age:20

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zwpjyp.html