1. UnSafe 简介
Unsafe
是 Java 整个并发包底层实现的核心,它具有像 C++ 的指针一样直接操作内存的能力,而这也就意味着其越过了 JVM 的限制。Unsafe
的特性可归结如下,它虽然在一定程度上提升了效率,但是也带来了指针的不安全性
Unsafe
不受 JVM 管理,也就无法被自动 GC,需要手动 GC,容易出现内存泄漏
Unsafe
的大部分方法中必须提供原始地址(内存地址)和被替换对象的地址,偏移量需自行计算,一旦出现问题必然是 JVM 崩溃级别的异常,会导致整个应用程序直接 crash
- 直接操作内存,也意味着其速度更快,在高并发的条件之下能够很好地提高效率
2. UnSafe 功能与使用示例
Unsafe
类的构造函数是私有的,而对外提供的静态方法Unsafe#getUnsafe()
又对调用者的 ClassLoader
有限制 ,如果这个方法的调用者不是由 Boot ClassLoader 加载的,则会报错
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
因为 Java 源码中由开发者自定义的类都是由 Appliaction ClassLoader
加载的,所以正常情况下我们无法直接使用Unsafe
,如果需要使用它,则需要利用反射
public static Unsafe getUnsafe() {
Unsafe unsafe = null;
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe) field.get(null);
} catch (Exception e) {
e.printStackTrace();
}
return unsafe;
}
2.1 操作对象属性
方法 |
功能 |
public native Object getObject(Object o, long offset) |
获取一个Java 对象中偏移地址为 offset 的属性的值,此方法可以突破修饰符的限制,类似的方法有 getInt()、getDouble() 等,同理还有 putObject() 方法 |
public native Object getObjectVolatile(Object o, long offset) |
强制从主存中获取目标对象指定偏移量的属性值,类似的方法有getIntVolatile()、getDoubleVolatile() 等,同理还有putObjectVolatile() |
public native void putOrderedObject(Object o, long offset, Object x) |
设置目标对象中偏移地址 offset 对应的对象类型属性的值为指定值。这是一个有序或者有延迟的 putObjectVolatile() 方法,并且不保证值的改变被其他线程立即看到。只有在属性被volatile 修饰并且期望被修改的时候使用才会生效,类似的方法有 putOrderedInt() 和 putOrderedLong() |
public native long objectFieldOffset(Field f) |
返回给定的非静态属性在它的类的存储分配中的位置(偏移地址),然后可根据偏移地址直接对属性进行修改,可突破属性的访问修饰符限制 |
private String name;
@Test
public void test() {
Unsafe unsafe = getUnsafe();
try {
DirectMemory directMemory = (DirectMemory) unsafe.allocateInstance(DirectMemory.class);
long nameOffset = unsafe.objectFieldOffset(DirectMemory.class.getDeclaredField("name"));
unsafe.putObject(directMemory, nameOffset, "luck");
System.out.println(directMemory.getName());
} catch (Exception e) {
e.printStackTrace();
}
}
2.2 操作数组元素
方法 |
功能 |
public native int arrayBaseOffset(Class arrayClass) |
返回数组类型的第一个元素的偏移地址(基础偏移地址) |
public native int arrayIndexScale(Class arrayClass) |
返回数组中元素与元素之间的偏移地址的增量,配合 arrayBaseOffset() 使用就可以定位到任何一个元素的地址 |
private String[] names = {
"nathan", "goog", "luck"};
@Test
public void test() {
Unsafe unsafe = getUnsafe();
try {
Class<?> a = String[].class;
int base = unsafe.arrayBaseOffset(a);
int scale = unsafe.arrayIndexScale(a);
System.out.println(unsafe.getObject(names, (long) base + 2 * scale));
} catch (Exception e) {
e.printStackTrace();
}
}
在 ForkJoinPool
中有一种新的元素定位算法,这种方法要求 scale
也就是元素的内存大小为 2 的次幂。假如 scale
等于 4,那么 ASHIFT 值为 2,乘以 4 和向左移 2 位结果是一样的,但是移位操作显然更高效
ASHIFT = 31 - Integer.numberOfLeadingZeros(scale)
offset = (i << ASHIFT) + ABASE
2.3 内存地址操作
方法 |
功能 |
public native int addressSize() |
获取本地指针的大小(单位是byte),通常值为 4 或者 8 |
public native int pageSize() |
获取本地内存的页数,此值为2的幂次方 |
public native long allocateMemory(long bytes) |
分配一块新的本地内存,通过bytes指定内存块的大小(单位是byte),返回新开辟的内存的地址 |
public native long reallocateMemory(long address, long bytes) |
通过指定的内存地址 address 重新调整本地内存块的大小,调整后的内存块大小通过bytes指定(单位为byte) |
public native void setMemory(Object o, long offset, long bytes, byte value) |
将给定内存块中的所有字节设置为固定值(通常是0) |
public class DirectMemoryBuffer {
private final static int BYTE = 1;
private long size;
private long address;
private Unsafe unsafe;
public DirectMemoryBuffer(long size, Unsafe unsafe) {
this.size = size;
this.unsafe = unsafe;
address = unsafe.allocateMemory(size * BYTE);
}
public void set(long i, byte value) {
unsafe.putByte(address + i * BYTE, value);
}
public int get(long idx) {
return unsafe.getByte(address + idx * BYTE);
}
public long size() {
return size;
}
public void freeMemory() {
unsafe.freeMemory(address);
}
}
2.4 CAS 操作
CAS 是一种乐观锁机制,它不需要抢占锁,能有效地提高效率,依赖于硬件的原子操作实现
方法 |
功能 |
public final native boolean compareAndSwapObject(Object target, long offset, Object exceptData, Object targetData) |
其作用为比较目标对象指定偏移量的属性的期望值与主存中的值,如果二者相等,则将主存中的值更新为目标值。同样还有 compareAndSwapInt() 和 compareAndSwapLong() |
public class CASCounter {
private Unsafe unsafe;
private volatile long count = 0;
private long offset;
public CASCounter(Unsafe unsafe) {
this.unsafe = unsafe;
try {
offset = unsafe.objectFieldOffset(CASCounter.class.getDeclaredField("count"));
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
public void increment() {
long before = count;
while (!unsafe.compareAndSwapLong(this, offset, before, before + 1)) {
before = count;
}
}
public long getCount() {
return count;
}
}
2.5 线程挂起与恢复
方法 |
功能 |
public native void park(boolean isAbsolute, long time) |
阻塞当前线程,一直等到 unpark() 方法被调用或者超时,和 Object.await() 非常类似,但是 park 是操作系统调用,因此在某些操作系统架构上这会带来更好的性能 |
public native void unpark(Object thread) |
唤醒被 park 阻塞的线程,由于其不安全性,因此必须保证线程是存活的 |
2.6 内存屏障
方法 |
功能 |
public native void loadFence() |
如果不要volatile去增加内存屏障,即可用该方法手动增加屏障。在该方法之前的所有读操作,一定在load屏障之前执行完成 |
public native void storeFence() |
在该方法之前的所有写操作,一定在store屏障之前执行完成 |
public native void fullFence() |
在该方法之前的所有读写操作,一定在 full 屏障之前执行完成,这个内存屏障相当于上面两个(load屏障和store屏障)的合体功能 |
2.7 Class 相关
方法 |
功能 |
public native boolean shouldBeInitialized(Class<?> c) |
判断是否需要初始化一个类,通常在获取一个类的静态属性的时候使用(一个类如果没初始化,它的静态属性也不会初始化)。 当且仅当ensureClassInitialized方法不生效时返回false |
public native void ensureClassInitialized(Class<?> c) |
检测给定的类是否已经初始化,通常在获取一个类的静态属性的时候使用(因为一个类如果没初始化,它的静态属性也不会初始化) |
public native Class<?> defineClass(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain) |
定义一个类,此方法会跳过JVM的所有安全检查,默认情况下 ClassLoader(类加载器)和 ProtectionDomain(保护域)实例来源于调用者 |
public native Class<?> defineAnonymousClass(Class<?> hostClass, byte[] data, Object[] cpPatches) |
定义一个匿名类 |
public native long staticFieldOffset(Field f) |
返回给定的静态属性在它的类的存储分配中的位置(偏移地址) |
public native Object staticFieldBase(Field f) |
返回给定的静态属性的位置,配合 staticFieldOffset() 方法使用 |