jdk.internal.misc.Unsafe和sun.misc.Unsafe介绍

2024-11-14 01:26
11
0

sun.misc.Unsafe:在早期的JDK中,许多框架和库(如juc包中的类)依赖于sun.misc.Unsafe来实现高效的并发控制和内存操作。它提供一些低级别的操作,如直接内存访问、线程调度、CAS(Compare-And-Swap)操作等。功能强大,但也非常危险,绕过Java的安全机制,直接操作内存,容易导致崩溃或安全漏洞。然而,sun.misc.Unsafe是一个非标准API(在jdk.unsupported包下),并不是Java标准库的一部分,使用它的代码在不同的JDK版本之间可能不兼容。为了减少这种不兼容性,JDK开发者逐渐将对sun.misc.Unsafe的依赖移除或替换。

jdk.internal.misc.Unsafe:为了应对sun.misc.Unsafe的非标准化问题,JDK 9引入模块化系统(Project Jigsaw),并将一些内部API移动到jdk.internal包中。jdk.internal.misc.Unsafe是sun.misc.Unsafe的替代品,提供类似功能,但位于一个内部的、更明确的命名空间中。

两者区别

  • 命名空间和访问控制:jdk.internal.misc.Unsafe位于jdk.internal模块中,使用更严格的访问控制。只有在明确声明模块依赖的情况下才能访问,而sun.misc.Unsafe则是一个开放的、非标准API
  • API变化:虽然两者提供的功能类似,但它们的API可能会有所不同。随着JDK的发展,jdk.internal.misc.Unsafe可能会引入新的方法或对现有方法进行调整,以适应内部需求
  • 未来发展方向:jdk.internal.misc.Unsafe是JDK团队为了模块化和安全性而进行的改进。未来,更多内部功能可能会转移到jdk.internal命名空间中,而sun.misc.Unsafe则可能逐渐被废弃或减少使用。

一、sun.misc.Unsafe

如果使用JDK9之前的版本,那么接触更多的还是sun.misc.Unsafe类。

Unsafe 类为一单例实现,提供静态方法 getUnsafe 获取 Unsafe实例。这个看上去貌似可以用来获取 Unsafe 实例。但是,当我们直接调用这个静态方法的时候,会抛出 SecurityException 异常:

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)

为什么public static方法无法被直接调用呢

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

为什么要对 Unsafe 类进行这么谨慎的使用限制呢?

Unsafe提供的功能过于底层(如直接访问系统内存资源、自主管理内存资源等),安全隐患也比较大,使用不当的话,很容易出现很严重的问题。

如若想使用 Unsafe 这个类的话,应该如何获取其实例呢?

  1. 通过反射获取Unsafe类种已经实例化完成的单例对象。
  2. 从getUnsafe方法的使用限制条件出发,通过 Java 命令行命令-Xbootclasspath/a把调用 Unsafe 相关方法的类 A 所在 jar 包路径追加到默认的 bootstrap 路径中,使得 A 被引导类加载器加载,从而通过Unsafe.getUnsafe方法安全的获取 Unsafe 实例。
java -Xbootclasspath/a: ${path}   // 其中path为调用Unsafe相关方法的类所在jar包路径

详细的sun.misc.unsafe类相关说明可以参考此篇文章

二、jdk.internal.misc.Unsafe

如果是使用JDK9(包含)之后的版本,那么就要使用jdk.internal.misc.Unsafe类。

但如下图所示,jdk.internal.misc.Unsafe类在java.base基础模块下,未对外开放,不能直接引用。

从Java9开始,Java引入了模块系统,该系统限制了模块之间的可见性和访问性。官方对使用的是Java 9或更高版本的程序,建议应该避免使用内部API,因为它们可能在未来的版本中改变或者被移除。如果确实如果你有合理的理由需要使用这个内部API,在此提供两种可以调用的方式。

(JDK模块相关知识网上很多,这里随便找了一篇文章仅供参考)

首先,针对上图编译器报红的问题,需要在编译配置如下命令:--add-exports java.base/jdk.internal.misc=ALL-UNNAMED,表示jdk.internal.misc包导出,对所有未命名模块开放。(如果你的类在你自定义的模块下面,那ALL-UNNAMED就改成你的模块名即可)

方法一:在VM Options中添加:--add-exports java.base/jdk.internal.misc=ALL-UNNAMED,即可进行直接调用。

执行如下示例代码,正常执行。

public static void main(String[] args) {
        jdk.internal.misc.Unsafe unsafe = jdk.internal.misc.Unsafe.getUnsafe();
        System.out.println(unsafe.addressSize());
    }

方法二:VM Options中添加:--add-opens java.base/jdk.internal.misc=ALL-UNNAMED,即可通过反射获得Unsafe类的单例对象。

public static void main(String[] args) throws Exception {
        // 通过反射获取Unsafe类
        Class<?> unsafeClass = Class.forName("jdk.internal.misc.Unsafe");
        Field field = unsafeClass.getDeclaredField("theUnsafe");
        field.setAccessible(true);
        Unsafe unsafe = (Unsafe) field.get(null);
        System.out.println(unsafe.addressSize());
    }

三、参考文献

JDK9的Unsafe:https://blog.csdn.net/lonelymanontheway/article/details/140413151

JDK8的Unsafe:https://javaguide.cn/java/basis/unsafe.html

JDK模块:https://blog.csdn.net/weiweiqiao/article/details/142768639

 

 

全部评论