简要介绍:
Java语言先比较与C和C++有一个非常大的不同点在于Java语言无法直接操作内存,实际开发中,默认都是由JVM来进行内存分配和垃圾回收,而JVM在进行垃圾回收的时候,绝大多数垃圾回收器都需要STW(stop the world)这个问题往往会导致服务短暂或者较长时间的暂停。因此Unsafe提供了通过Java直接操作内存的API,尽管Unsafe是JavaNIO和并发的核心类,但是其如其名,这是一个官方不推荐开发者使用的及其不安全的类!
主要作用:
序号 |
作用 |
API |
1 |
内存管理。(包括分配内存、释放内存等。) |
allocateMemory(分配内存)、reallocateMemory(重新分配内存)、copyMemory(拷贝内存)、freeMemory(释放内存 )、getAddress(获取内存地址)、addressSize、pageSize、getInt(获取内存地址指向的整数)、getIntVolatile(获取内存地址指向的整数,并支持volatile语义)、putInt(将整数写入指定内存地址)、putIntVolatile(将整数写入指定内存地址,并支持volatile语义)、putOrderedInt(将整数写入指定内存地址、有序或者有延迟的方法) |
2 |
非常规的对象实例化 |
allocateInstance()方法提供了另一种创建实例的途径 |
3 |
操作类、对象、变量 |
staticFieldOffset(静态域偏移)、defineClass(定义类)、defineAnonymousClass(定义匿名类)、ensureClassInitialized(确保类初始化)、objectFieldOffset(对象域偏移) |
4 |
数组操作 |
arrayBaseOffset(获取数组第一个元素的偏移地址)、arrayIndexScale(获取数组中元素的增量地址)等方法 |
5 |
多线程同步。包括锁机制、CAS操作等 |
monitorEnter、tryMonitorEnter、monitorExit、compareAndSwapInt、compareAndSwap |
6 |
挂起与恢复 |
park、unpark |
7 |
内存屏障 |
loadFence、storeFence、fullFence |
一、获取Unsafe
源码-基于jdk1.8
/*
* 在Unsafe源码中限制了获取Unsafe的ClassLoader,如果这个方法调用实例不是由BootStrap类加载器加载的,则会报错
* 因此,我们如果需要使用Unsafe类,可以通过反射的方式来获取。
*/
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
// 此处会判断ClassLoader是否为空,BootStrap由C语言编写,在Java中获取会返回null。
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
获取方式
/**
* 反射获取Unsafe
*
* @return Unsafe
*/
public static final Unsafe getUnsafe() {
Unsafe unsafe = null;
try {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
unsafe = (Unsafe) theUnsafe.get(null);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
return unsafe;
}
二、操作方法
数组操作
package com.liziba.unsafe;
import sun.misc.Unsafe;
/**
* <p>
* 操作数组示例
* </p>
*
* @Author: Liziba
* @Date: 2021/5/23 13:33
*/
public class OperateArrayExample {
/**
* 1、public native int arrayBaseOffset(Class<?> var1); 获取数组第一个元素的偏移地址
* 2、public native int arrayIndexScale(Class<?> var1); 获取数组中元素的增量地址
* 3、public Object getObject(Object var1, int var2); 通过对象和地址偏移量获取元素
*/
public static void operateArrayUseUnsafe() {
// 测试数组
String[] exampleArray = new String [] {"李" , "子", "捌"};
Unsafe unsafe = UnsafeFactory.getUnsafe();
// 获取数组的基本偏移量
int baseOffset = unsafe.arrayBaseOffset(String[].class);
System.out.println("String[] base offset is : " + baseOffset);
// 获取数组中元素的增量地址
int scale = unsafe.arrayIndexScale(String[].class);
System.out.println("String[] index scale is : " + scale);
// 获取数组中第n个元素 i = (baseOffset + (scale * n-1))
System.out.println("third element is :" + unsafe.getObject(exampleArray, baseOffset + (scale * 2)));
// 修改数组中第n个元素 i = (baseOffset + (scale * n-1))
unsafe.putObject(exampleArray, baseOffset + scale * 2, "柒");
System.out.println("third element is :" + unsafe.getObject(exampleArray, baseOffset + (scale * 2)));
}
public static void main(String[] args) {
OperateArrayExample.operateArrayUseUnsafe();
}
}
输出结果
对象操作
package com.liziba.unsafe;
import com.liziba.unsafe.pojo.User;
import sun.misc.Unsafe;
import java.io.File;
import java.io.FileInputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
/**
* <p>
* 操作对象示例
* </p>
*
* @Author: Liziba
* @Date: 2021/5/24 20:40
*/
public class OperateObjectExample {
/**
* 1、public native Object allocateInstance(Class<?> var1); 分配内存
* 2、public native Class<?> defineClass(String var1, byte[] var2, int var3, int var4, ClassLoader var5, ProtectionDomain var6); 方法定义一个类用于动态的创建类
* @throws Exception
*/
public static void operateObjectUseUnsafe() throws Exception{
Unsafe unsafe = UnsafeFactory.getUnsafe();
// 使用Unsafe的allocateInstance()方法,可以无需使用构造函数的情况下实例化对象
User user = (User) unsafe.allocateInstance(User.class);
user.setId(1);
user.setName("李子捌");
System.out.println(user);
// 返回对象成员属性在内存中相对于对象在内存中地址的偏移量
Field name = User.class.getDeclaredField("name");
long fieldOffset = unsafe.objectFieldOffset(name);
// 使用Unsafe的putXxx()方法,可以直接修改内存地址指向的数据(可以越过权限访问控制符)
unsafe.putObject(user, fieldOffset, "李子柒");
System.out.println(user);
// 使用Unsafe在运行时通过.class文件,创建类
File classFile = new File("E:\\workspaceall\\liziba-javap5\\out\\production\\liziba-javap5\\com\\liziba\\unsafe\\pojo\\User.class");
FileInputStream fis = new FileInputStream(classFile);
byte [] classContent = new byte[(int) classFile.length()];
fis.read(classContent);
Class<?> clazz = unsafe.defineClass(null, classContent, 0, classContent.length, null, null);
Constructor<?> constructor = clazz.getDeclaredConstructor(int.class, String.class);
System.out.println(constructor.newInstance(1, "李子玖"));
}
public static void main(String[] args) {
try {
OperateObjectExample.operateObjectUseUnsafe();
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出结果
内存操作
package com.liziba.unsafe;
import sun.misc.Unsafe;
/**
* <p>
* 內存地址操作示例
* </p>
*
* @Author: Liziba
* @Date: 2021/5/24 21:32
*/
public class OperateMemoryExample {
/**
* 1、public native long allocateMemory(long var1); 分配var1字节大小的内存,返回起始地址偏移量
* 2、public native long reallocateMemory(long var1, long var3); 重新给var1起始地址的内存分配长度为var3字节的内存,返回新的内存起始地址偏移量
* 3、public native void freeMemory(long var1); 释放起始地址为var1的地址
*
* 分配地址的方法还有重分配,都是分配在堆外内存,返回的是一个long类型的地址偏移量。这个偏移量在Java程序中的每一块内存都是唯一的
*
*/
public static void operateMemoryUseUnsafe() {
Unsafe unsafe = UnsafeFactory.getUnsafe();
// 申请分配8byte的内存
long address = unsafe.allocateMemory(1L);
// 初始化内存填充值
unsafe.putByte(address, (byte)1);
// 测试输出
System.out.println(new StringBuilder().append("address: ").append(address).append(" byte value: ").append(unsafe.getByte(address)));
// 重新分配一个地址
long newAddress = unsafe.reallocateMemory(address, 8L);
unsafe.putLong(newAddress, 8888L);
System.out.println(new StringBuilder().append("address: ").append(newAddress).append(" long value: ").append(unsafe.getLong(newAddress)));
// 释放地址,注意地址可能被其他使用
unsafe.freeMemory(newAddress);
System.out.println(new StringBuilder().append("address: ").append(newAddress).append(" long value: ").append(unsafe.getLong(newAddress)));
}
public static void main(String[] args) {
OperateMemoryExample.operateMemoryUseUnsafe();
}
}
输出结果
CAS操作
package com.liziba.unsafe;
import com.liziba.unsafe.pojo.User;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
/**
* <p>
* CAS操作示例
* </p>
*
* @Author: Liziba
* @Date: 2021/5/24 22:18
*/
public class OperateCASExample {
/**
* CAS == compare and swap(比较并替换)
* 当需要改变的值为期望值的时候,就替换为新的值,是原子(不可再分割)操作。Java中大量的并发框架底层使用到了CAS操作。
* 优势:无锁操作,减少线程切换带来的开销
* 缺点:CAS容易在并发的情况下失败从而引发性能问题,也存在ABA问题。
*
* Unsafe中提供了三个方法
* 1、compareAndSwapInt
* 2、compareAndSwapLong
* 3、compareAndSwapObject
*
*/
public static void operateCASUseUnsafe() throws Exception {
User user = new User(1, "李子捌");
System.out.println("pre user value: " + user);
Unsafe unsafe = UnsafeFactory.getUnsafe();
Field id = user.getClass().getDeclaredField("id");
Field name = user.getClass().getDeclaredField("name");
// 获取ID字段的内存偏移量
long idFieldOffset = unsafe.objectFieldOffset(id);
// 获取name字段的内存偏移量
long nameFieldOffset = unsafe.objectFieldOffset(name);
// 如果ID的期望值是1,则修改为18 success
unsafe.compareAndSwapInt(user, idFieldOffset, 1, 18);
// 如果name的期望值是小荔枝,则修改为李子柒 fail
unsafe.compareAndSwapObject(user, nameFieldOffset, "小荔枝", "李子柒");
// 输出修改的user对象
System.out.println("post user value: " + user);
}
public static void main(String[] args) {
try {
OperateCASExample.operateCASUseUnsafe();
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出结果
线程的挂起和恢复
/**
* 查看Java的java.util.concurrent.locks.LockSupport源代码可以发现LockSupport类
* 中有各种版本的pack方法但是最终都是通过调用Unsafe.park()方法实现的。
*/
public class LockSupport {
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
setBlocker(t, null);
}
public static void parkNanos(Object blocker, long nanos) {
if (nanos > 0) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, nanos);
setBlocker(t, null);
}
}
public static void parkNanos(long nanos) {
if (nanos > 0)
UNSAFE.park(false, nanos);
}
public static void parkUntil(Object blocker, long deadline) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(true, deadline);
setBlocker(t, null);
}
public static void parkUntil(long deadline) {
UNSAFE.park(true, deadline);
}
}