本文整理完善自下面这两篇优秀的文章:
Java魔法类:Unsafe应用解析-美团技术团队-2019
Java双刃剑指Unsafe类1详解-码农参上-2021
阅读过JUC源码的同学,一定会发现很多并发工具类都调用了一个叫做Unsafe的类
那这个类主要是用来干什么的呢?
有什么使用场景?
这篇文章就带你搞清楚!
Unsafe介绍
Unsafe是位于sun.misc包下的一个类,主要提供一些用于执行低级别,不安全操作的方法,如直接访问系统内存资源,自主管理内存资源等,这些方法在提升Java运行效率,增强Java语言底层资源操作能力方面起到了很大的作用。
但由于Unsafe类使Java语言拥有了类似C语言指针一样操作内存空间的能力,这无疑也增加了程序发生相关指针问题的风险。
在程序中过度,不正确使用Unsafe类会使得程序出错的概率变大,使得Java这种安全的语言变得不再“安全”,因此对Unsafe的使用一定要慎重。
另外,Unsafe提供的这些功能的实现需要依赖本地方法(Native Method).
你可以将本地方法看作是Java中使用其他编程语言编写的方法。
本地方法使用native关键字修饰,Java代码只是声明方法头,具体的实现则交给本地代码
为什么要使用本地方法呢?
1.需要用到Java中不具备的依赖于操作系统的特性,Java在实现跨平台的同时要实现对底层的控制,需要借助其他语言发挥作用。
2.对于其他语言已经完成的一些现成功能,可以使用Java直接调用。
3.程序对时间敏感或对性能要求非常高时,有必要使用更加底层的语言,例如C/C++甚至是汇编。
在JUC包的很多并发工具类的实现并发机制时,都调用了本地方法,通过它们打破了Java运行时的界限,能够接触到操作系统底层的某些功能。
对于同一本地方法,不同的操作系统可能会通过不同的方法来实现,但是对于使用者来说是透明的,最终都会得到相同的结果。
Unsafe创建
sum.misc.Unsafe部分源码如下:
public final class Unsafe {
// 单例对象
private static final Unsafe theUnsafe;
.....
private Unsafe(){
}
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
// 仅在引导加载器`BootstrapClassLoader`加载时才合法
if(!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
}
Unsafe类为——单例实现,提供静态方法getUnsafe实例。
这个看上去貌似可以用来获取Unsafe实例。
但是,当我们直接调用这个静态方法的时候,会抛出SecurityException异常:
Exception in thread "main" java.lang.SecurityException:Unsafe
at sum.misc.Unsafe.getUnsafe(Unsafe.java:90)
at com.cn.test.GetUnsafeTest.main(GetUnsafeTest.java:12)
为什么public static方法无法被直接调用呢?
这是因为在getUnsafe方法中,会对调用者的classLoader进行检查,判断当前类是否由BootstrapclassLoader加载,如果不是的话那么就会抛出一个SecurityException异常。
也就是说,只有启动类加载器加载的类才能够调用Unsafe类中的方法,来防止这些方法在不可信的代码中被调用。
为什么要对Unsafe类进行这么谨慎的使用限制呢?
Unsafe提供的功能过于底层(如直接访问系统内存资源,自主管理内存资源等),安全隐患也比较大,使用不当的话,很容易出现很严重的问题。
如若想使用Unsafe这个类的话,应该如何获取其实例呢?
这里介绍两个可行的方案。
1.利用反射获得Unsafe类中已经实例化完成的单例对象theUnsafe.
private static Unsafe reflectGetUnsafe() {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe) field.get(null);
} catch (Exception e) {
long.error(e.getMessage(),e);
return null;
}
}
2.从getUnsafe方法的使用限制条件出发,通过Java命令行命令-Xbootclasspath/a把调用Unsafe相关方法的类A所在jar包路径追加到默认的bootstrap路径中,使得A被引导类加载器加载,从而通过Unsafe.getUnsafe方法安全的获取Unsafe实例。
java -Xbootclasspath/a: ${path} // 其中path为调用Unsafe相关方法的类所在jar包路径
Unsafe功能
概括的来说,Unsafe类实现功能可以被分为下面8类:
1.内存操作
2.内存屏蔽
3.对象操作
4.数据操作
5.CAS操作
6.线程调度
7.Class操作
8.系统信息
内存操作
介绍
如果你是一个写过C或者C++的程序员,一定对内存操作不陌生,而在Java中是不允许直接对内存进行操作的,对象内存的分配和回收都是由JVM自己实现的。
但是在Unsafe中,提供的下列接口可以直接进行内存操作:
//分配新的本地空间
public native long allocateMemeory(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 secOffset,Object destBase,long destOffset,long bytes);
//清楚内存
public native void freeMemory(long address);
使用下面代码进行测试:
private void memoryTest() {
int size = 4;
long addr = unsafe.allocateMemory(size);
long addr3 = unsafe.reallocateMemory(addr,size*2);
System.out.println("addr:"+addr);
System.out.println("addr3:"+ addr3);
try {
unsafe.setMemory(null,addr,size,(byte)1);
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.freeMemory(addr);
unsafe.freeMemory(addr3);
}
}
先看结果输出:
addr:2433733895744
addr3:2433733894944
16843009
72340172838076673
分析一下运行结果,首先使用allocationMemory方法申请4字节长度的内存空间,调用setMemory方法向每个字节写入内容为byte类型的1,当使用Unsafe调用getInt方法时,因为一个int型变量占4个字节,会一次性读取4个字节,组成一个int的值,对应的十进制结果为16843009.
你可以通过下图理解这个过程: