Java魔法类Unsafe详解

本文整理完善自下面这两篇优秀的文章:

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.

你可以通过下图理解这个过程:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值