Java中的Unsafe类

Java中的Unsafe类

Unsafe类时sun,misc包下的一个类,主要用于执行一些 Native 方法,同时它赋予了 Java 直接操作内存的能力,但是直接操作内存的能力同时也破坏了Java的安全性,可能这就是为什么叫 Unsafe 吧

关于Native 方法

Native 方法是没有方法体的,是为了调用其他语言写的( C/C++ )代码而写的函数,如果了解 JVM 的话,就知道线程内部不仅有虚拟机栈,还有一个本地方法栈,虚拟机栈是用来存放非本地方法调用时的栈帧,本地方法栈就是存放本地方法调用时的栈帧

Unsafe 获取

Unsafe类中定义了一个 getUnsafe() 方法,但是如果直接调用会抛出如下异常

Exception in thread "main" java.lang.SecurityException: Unsafe

这是因为 Unsafe 源码中 getUnsafe() 方法是如下定义

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

// 可以看到会先判断是不是系统类加载器调用的该方法,否则抛出SecurityException异常

直接获取不了只能使用反射的方法获取
在 Unsafe 类中定义了如下变量 ( theUnsafe )

private static final Unsafe theUnsafe = new Unsafe();

所以可以直接使用如下方式获取到 Unsafe 类对象

private static Unsafe unsafe;

private void init() throws NoSuchFieldException, IllegalAccessException {
    Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
    theUnsafe.setAccessible(true);
    unsafe = (Unsafe) theUnsafe.get(null);
}
Unsafe 中常用的方法
//分配新的本地空间
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);


// 获取对象属性偏移量
public native long objectFieldOffset(Field field);
// 获取对象静态属性偏移量
public native long staticFieldOffset(Field field);
// 获取静态对象的基址
public Object staticFieldBase(Field field);


//获取数组的基址
public native int arrayBaseOffset(Class<?> arrayClass);
//获取数组的元素占用大小
public native int arrayIndexScale(Class<?> arrayClass);


// 比较并更新一个Object属性(CAS操作)
public final native boolean compareAndSwapObject(Object o, long offset,  Object expected, Object update);
public final native boolean compareAndSwapInt(Object o, long offset, int expected,int update);
public final native boolean compareAndSwapLong(Object o, long offset, long expected, long update);
...
练习

给一个题目:轮转数组

给定一个数组,将数组中的元素向左移动 k 个位置,其中 k 是非负数。
输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [4,5,6,7,1,2,3]

关于这一题其实有多种解法,但是这里主要尝试使用Unsafe类来解这个题
给定如下思路:
创建一个nums二倍长度的数组,存放两次nums中的数
例:

nums = {1,2,3,4,5,6}, k = 3
// 两倍数组并赋值后
nums2 = {1,2,3,4,5,6,1,2,3,4,5,6}
// 现在要得到目标数组,只需要从 nums2 中的第 k + 1 个元素开始截取,即可获得
nums2 => {1,2,3, | 4,5,6,1,2,3 | 4,5,6} => {4,5,6,1,2,3}

实现如下:

public int[] solution(int[] nums,int k) throws IllegalAccessException, NoSuchFieldException {
      // 扩容后的数组
      int[] nums2 = new int[nums.length * 2];
      int len = nums.length;
      // 利用反射获取 Unsafe 类
      Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
      theUnsafe.setAccessible(true);
      Unsafe unsafe = (Unsafe) theUnsafe.get(null);
      // 获取int[]的偏移地址
      int offset = unsafe.arrayBaseOffset(int[].class);
      // 获取int[]中每个元素的大小
      int size = unsafe.arrayIndexScale(int[].class);
      // 将nums中的元素内容复制到nums2中,复制两次让nums2 为两次 nums连接
      unsafe.copyMemory(nums,offset,nums2,offset,size * len);
      unsafe.copyMemory(nums,offset, nums2 ,offset + size * len, size * len);
      // 从第 k 个元素开始取 len 个元素复制回 nums
      unsafe.copyMemory(nums2,offset + k * size,nums, offset ,size * len);
      return nums;
  }
posted @ 2023-02-27 08:32  PupilXIao  阅读(81)  评论(0编辑  收藏  举报