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;
}