首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >深入理解Unsafe类

深入理解Unsafe类

作者头像
苏三说技术
发布2025-08-06 18:26:11
发布2025-08-06 18:26:11
9900
代码可运行
举报
文章被收录于专栏:苏三说技术苏三说技术
运行总次数:0
代码可运行

Unsafe 类位于 sun.misc 包中,sun.misc 包本身在工作中就是个很少被用到的包。

在 Java 的发展中,sun.misc 包是 Sun 公司早年的内部工具包,提供了很多底层操作系统级别的方法调用,拥有很大的权限。

然而,大多数开发手册都不推荐使用 sun.misc 包,因为直接使用 sun.misc 包下的类,可能会带来安全风险和不可控性。

还记得 Java 和 C 语言相比有什么优势吗?

Java 中是没有指针的。在程序中维护 C 语言指针的经历一定曾让你焦头烂额,而 Java 语言中避免了这种指针操作,这就使得编码的安全性、效率得到大大地提升。

现在,Java 通过 Unsafe 保留了对指针的操作能力。这看上去有点前后矛盾,好像说不要指针的是 Java,说要指针的也是 Java。然而,那么多优秀框架底层都用了 Unsafe,那自然是有它适合的场景。

接下来,我们就来讲讲 Unsafe 类的创建和它的两个常见的应用场景。

创建 Unsafe

我们先来查看一下 Unsafe 的源码。

代码语言:javascript
代码运行次数:0
运行
复制
public finalclass Unsafe {
privatestaticfinal Unsafe theUnsafe;
  ......
private Unsafe() {
  }
@CallerSensitive
public static Unsafe getUnsafe() {
    Class var0 = Reflection.getCallerClass();
    if(!VM.isSystemDomainLoader(var0.getClassLoader())) {
      thrownew SecurityException("Unsafe");
    } else {
      return theUnsafe;
    }
  }
}

getUnsafe 似乎可以直接获取一个 Unsafe 对象,然而实际调用后,getUnsafe 方法一定会抛出 SecurityException 异常。这是因为 isSystemDomainLoader 方法会对调用者的 ClassLoader 进行检查,如果调用者的 ClassLoader 不是 BootStrap ClassLoader,调用者就会抛出 SecurityException 异常。

也就是说,只有 JDK 自己的类才可以使用 getUnsafe 来获取 Unsafe 实例,我们工程师自己的方法是没有权限调用 getUnsafe 方法的。

这种情况下,我们如何获取 Unsafe 实例呢?这里有两个方案,我们来一起看一下。

方案一,利用反射。在 Unsafe 的源码中,有一个 Unsafe 类型的成员变量——theUnsafe,我们可以通过反射来直接获取这个变量。

代码语言:javascript
代码运行次数:0
运行
复制
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);

因为 theUnsafe 是 private 修饰的,所以我们可以直接用 setAccessible 强制打开访问权限,这样就绕开了层层封锁,可以直接获取 Unsafe 对象了。

方案二,我们可以强制把我们的类放入 BootStrap ClassLoader 的 classpath。JDK 提供了-Xbootclasspath/a 命令允许我们把自己写的类加入 BootStrap ClassLoader 路径。这样就可以直接通过上面的 getUnsafe 方法获取 Unsafe 对象了。

千辛万苦创建了 Unsafe 之后,我们来继续看看 Unsafe 的使用场景。由于 Unsafe 的主要功能是管理内存,因此我们就来一起看看,Unsafe 是如何实现内存操作和内存屏障的。

内存操作

JVM 强大的一点功能是内存的自动管理,可以实现对象的自动回收。然而,一些特殊场景,如 NIO 的直接内存,并没有走 JVM 的自动内存管理。Unsafe 允许我们像 C 语言那样使用指针直接操作内存,它的 API 如下:

代码语言:javascript
代码运行次数:0
运行
复制
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);

其中,allocateMemory 是分配内存空间,reallocateMemory 方法可以重新调整内存空间大小,setMemory 可以设置内存的值,copyMemory 和 freeMemory 分别是拷贝和清除。这些方法和 C 语言几乎是对应的。

我们来看一个具体的例子吧。运行这段代码,会输出什么呢?

代码语言:javascript
代码运行次数:0
运行
复制
long addr = unsafe.allocateMemory(4);
unsafe.setMemory(null,addr ,size,(byte)1);
System.out.println(unsafe.getInt(addr));

输出的是 16843009。为什么会这样呢?

首先,unsafe.allocateMemory(4) 分配了一个 4 字节的空间,setMemory 则以 addr 为开始,以 addr+size 为结尾,向每个字节分别写入 1,这时候的内存空间是这样的:

getInt 方法会把结果转成 10 进制并返回,也就是 16843009。

需要注意的是,allocateMemory 分配的是堆外内存,是没有办法自动 GC 的,此时我们只能手动调用 freeMemory 方法才可以释放内存。对于上面的代码,我们可以在 finally 语句块中调用 freeMemory 来释放 addr。

代码语言:javascript
代码运行次数:0
运行
复制
finally {
        unsafe.freeMemory(addr);
        }

使用堆外内存有什么好处呢?

第一个显而易见的好处是减少了 GC。数据放在堆外内存,就和 GC 毫无关系了。

其次,提升了 I/O 操作的性能。我们读取文件或网络数据的时候,不可避免地需要在操作系统内存和 JVM 内存之间拷贝数据。虽然拷贝数据的这个过程是透明的,但占用了一定时间,直接使用堆外内存则减少了一次不必要的内存复制工作,进而提升了 I/O 整体性能。我们熟知的 DirectByteBuffer 底层就是基于 Unsafe 实现的。

内存屏障

接下来,我们再来看看 Unsafe 类在内存屏障场景中的应用。

说到内存屏障,我们就不得不提“指令重排序”了。在多线程中,“指令重排序”是一个经常被提到的概念,简单来说,就是操作系统在保证输出结果正确的情况下,对你的代码执行顺序进行调整,以提升系统执行性能。“指令重排序”的弊端在于它可能导致 CPU Cache 和内存中的数据不一致。

而内存屏障是制止重排序的指令,当然“指令重排序”的目标是为了优化执行性能,如果二话不说直接制止“指令重排序”也是不推荐的。只有当“指令重排序”影响正确结果的情况下,我们才去制止它。Unsafe 提供了下面 3 个内存屏障 API,你看一下:

代码语言:javascript
代码运行次数:0
运行
复制
public native void loadFence();
public native void storeFence();
public native void fullFence();

从名字上看,loadFence 作用于 JVM 的 Load 汇编指令,storeFence 作用于 JVM 的 Store 汇编指令,而 fullFence 同时会对 Load 和 Store 生效。对 JVM 汇编指令没有了解的同学可能认为 Load 就是读操作,Store 就是写操作。

对于这 3 个 API,我们用个形象的比喻来说明一下它们的作用吧。假设你要去做核酸检测,此时排起了长队,不时还出现插队现象,让人不堪其扰。于是,你在队伍中堆起了一堵高大的墙,墙两边的人依然会出现插队现象,但墙一边的人无法到达另一边,这就是屏障的作用。

换成更专业的表述就是屏障是一个同步点,使得同步点前的操作必然在同步点后的操作执行,同时屏障会使得 CPU Cache 中的数据失效,强制指令走内存读取数据。Java 中的 StampedLock 读写锁,就是使用了内存屏障来实现的。

总结

我们介绍了 Unsafe 的基本概念和创建方法,并讲了内存操作和内存屏障两个场景。通过这节课的学习,相信大家可以发现,Unsafe 能给我们带来实实在在的好处。当然,Unsafe 如同它的名称一样,存在不安全的隐患。然而,直到现在 Unsafe 依然存在。这说明,在正确使用的情况下,Unsafe 一定是利大于弊的。

最后讲一句,不到万不得已,不要轻易使用 Unsafe。我们讲解 Unsafe 是为了让大家对底层原理的理解更加深入透彻,至于在生产中应用 Unsafe,还要三思而后行。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-08-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 苏三说技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 创建 Unsafe
  • 内存操作
  • 内存屏障
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档