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() 方法使用 |