文章目錄
Unsafe-java的魔法類
Unsafe的介紹
Unsafe的源代碼:http://www.docjar.com/html/api/sun/misc/Unsafe.java.html
Unsafe類全限定名爲sun.misc.Unsafe
,顧名思義不是安全的。
一般而言,編寫底層代碼或者影響JVM是很難實現的,當然你可以使用JNI來達到目的,JNI需要和C打交道。
在java平臺通過sun.misc.Unsafe
的API,也可以進行底層編碼,比如操作目標對象的地址,直接修改屬性字段所在的地址的值…當然使用這個類比較危險,所以慎用。
Unsafe在原子類中大量使用到了,可以通過compareAndSwapXX
方法調用底層操作糸統的原子操作指令,來進行原子操作。
Unsafe對象的獲取
考慮創建Unsafe對象之前,先來看下這個類的源代碼:
public final class Unsafe {
private static final Unsafe theUnsafe;
private Unsafe() {
}
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
static {
theUnsafe = new Unsafe();
}
Unsafe 類有這麼一些特點:
- final修飾的
- 有一個靜態變量也是Unsafe類型的實例
theUnsafe
- 構造器私有
- 靜態方法getUnsafe()可以獲得一個Unsafe實例對象theUnsafe,但是檢查了類加載器(只有JVM的引導加載器才允許,否則拋出
SecurityException
異常) - 靜態代碼塊實例化了theUnsafe變量
那麼我們要創建Unsafe 的實例對象,怎麼做呢?
針對上面的特點針對性的解決。以下是兩種方案。
追加類到引導類加載器BoostrapClassloader
我們可以利用Unsafe類的靜態方法getUnsafe(),但是這個方法會檢查類加載器是否爲BoostrapClassloader。
所以我們可以將當前類所在路徑追加到BoostrapClassloader的掃描路徑下去。
這裏擴展一點: 得到BoostrapClassloader加載的路徑:
String property = System.getProperty("sun.boot.class.path"); for (String s : property.split(";")) { System.out.println(s); }
輸出:
C:\Program Files\Java\jdk1.8.0_211\jre\lib\resources.jar
C:\Program Files\Java\jdk1.8.0_211\jre\lib\rt.jar
C:\Program Files\Java\jdk1.8.0_211\jre\lib\sunrsasign.jar
C:\Program Files\Java\jdk1.8.0_211\jre\lib\jsse.jar
C:\Program Files\Java\jdk1.8.0_211\jre\lib\jce.jar
C:\Program Files\Java\jdk1.8.0_211\jre\lib\charsets.jar
C:\Program Files\Java\jdk1.8.0_211\jre\lib\jfr.jar
C:\Program Files\Java\jdk1.8.0_211\jre\classes
擴展:得到Extend類加載器加載的路徑:
String property2 = System.getProperty("java.ext.dirs"); for (String s : property2.split(";")) { System.out.println(s); }
輸出:
C:\Program Files\Java\jdk1.8.0_211\jre\lib\ext
C:\Windows\Sun\Java\lib\ext
Xbootclasspath擴展
Java 命令行提供瞭如何擴展BoostrapClassloader的簡單方法.
-Xbootclasspath: 新的jar
完全替換jdk的的Java class 搜索路徑,不建議;-Xbootclasspath/a:追加的jar
追加在jdk的java class搜索路徑後面,很實用(多個jar在unix冒號分隔,windows分號分隔);-Xbootclasspath/p:放在前面的jar
放jdk的java class搜索路徑前面,不建議;以免引起不必要的衝突。
所以我們可以通過在IDEA中設置jvm運行時參數(添加VM options):
-Xbootclasspath/a:D:\framework\concurrent\target\concurrent-1.0-SNAPSHOT.jar
再run即可。注意這裏要打包成jar,才能追加
/*
運行時添加 VM options
-Xbootclasspath/a:D:\framework\concurrent\target\concurrent-1.0-SNAPSHOT.jar
*/
Unsafe unsafe0 = Unsafe.getUnsafe();
System.out.println("-Xbootclasspath/a:添加jar包:" + unsafe0); // 可以獲得Unsafe實例對象
這種方式太難了…所以我們使用下面的第二種方式。
反射Field獲取Unsafe實例對象【推薦用法】
從Unsafe的特點來看,我們得知,其內部存在一個theUnsafe變量就是Unsafe的一個實例:
private static final Unsafe theUnsafe;
既然構造器不能用,我們就使用反射直接獲取該字段的值:
// 所以,選擇反射獲取
// 因爲內部存在一個單例實例:Unsafe theUnsafe;
Class clazz = Unsafe.class;
Field field = clazz.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe)field.get(null); // 得到其內部的字段theUnsafe
Unsafe的API
Unsafe提供了大量的API(100多個方法),來操作底層。
主要分爲這麼幾大類:
Info獲得信息
返回一些底層的內存信息。
- addressSize:本地指針大小,值一般爲4或者8;存儲在本地塊的原始類型由他們的內容信息決定。
- pageSize:返回內存頁大小,字節爲單位,值爲2的n次方
// info
System.out.println(unsafe.pageSize()); // 4096
System.out.println(unsafe.addressSize()); // 8
Objects操作對象
提供操作對象以及它的字段的方法。
-
allocateInstance:分配一個實例對象,但是不會調用任何的構造器(變量值沒有初始化)
-
public native Object allocateInstance(Class cls)
-
示例,很有意思:
@Data
public class UnsafeDemo {
private String name = "zs";
private int age = 18;
}
// 運行以下代碼的結果:
UnsafeDemo unsafeDemo3 = (UnsafeDemo)unsafe.allocateInstance(UnsafeDemo.class);
System.out.println(unsafeDemo3); // UnsafeDemo(name=null, age=0)
UnsafeDemo unsafeDemo1 = new UnsafeDemo();
System.out.println(unsafeDemo1); // UnsafeDemo(name=zs, age=18)
-
objectFieldOffset: 獲得某個類的某個字段在內存中的偏移量
-
public native long objectFieldOffset(Field f);
-
示例:獲得UnsafeDemo類的age字段在內存中的偏移量
long ageOffset =
unsafe.objectFieldOffset(UnsafeDemo.class.getDeclaredField("age"));
System.out.println(ageOffset); // 12
Classes操作類對象
提供操作類對象、靜態字段的方法。
-
staticFieldOffset:獲得靜態字段的偏移量
-
public native long staticFieldOffset(Field f);
-
示例:
@Data
public class UnsafeDemo {
private String name = "zs";
private int age = 18;
private static int status = 1;
}
// staticFieldOffset:獲得靜態字段的偏移量
Field status = UnsafeDemo.class.getDeclaredField("status");
long statusOffset = unsafe.staticFieldOffset(status);
System.out.println(statusOffset); // 104
-
defineClass : 告知JVM定義一個類,但是不要做安全檢查;默認情況下,類加載器和保護域來自調用者的Class。
-
public native Class defineClass(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain);
-
-
defineAnonymousClass:定義一個不被類加載器、系統感知的類。
-
/** * @params hostClass 鏈接的上下文,訪問控制,類加載器 * @params data 字節碼文件的字節數組形式 * @params cpPatches 如果存在非空數據,則替換data中的 829 */ */ public native Class defineAnonymousClass(Class hostClass, byte[] data, Object[] cpPatches);
-
-
ensureClassInitialized: 確保給定的類已經初始化,這通常需要結合獲取類的靜態字段庫。
-
public native void ensureClassInitialized(Class c);
-
Arrays操作數組
對數組的封裝。
-
arrayBaseOffset:數組對象的首元素的偏移量
-
public native int arrayBaseOffset(Class arrayClass)
-
示例:
int arrayBaseOffset =
unsafe.arrayBaseOffset(new byte[]{1, 2, 3}.getClass());
System.out.println(arrayBaseOffset); // 16
-
arrayIndexScale:尋址因子
-
public native int arrayIndexScale(Class arrayClass);
-
Synchronization操作同步字
操作同步的一些底層封裝。
-
monitorEnter:鎖定對象, 必須通過
monitorExit
釋放鎖。-
public native void monitorEnter(Object o);
-
-
tryMonitorEnter : 嘗試獲得鎖
-
public native boolean tryMonitorEnter(Object o);
-
-
monitorExit:釋放鎖
-
public native void monitorExit(Object o);
-
-
compareAndSwapInt:CAS也是很實用的一個方法,AtomicInteger、AtomicBoolean等原子類都是通過Unsafe的CAP方法實現原子操作的。
-
/** 原子操作:修改java變量的值爲x; 如果對象o的偏移量offset(其實就是該對象的某個字段)表示的變量的值,目前是期望值expected,則將其修改爲x,返回true; 如果目前是期望值不是expected,則不操作,返回false。 */ public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);
-
示例AtomicInteger#incrementAndGet方法的實現邏輯:
java.util.concurrent.atomic.AtomicInteger#incrementAndGet
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
sun.misc.Unsafe#getAndAddInt
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
// 自旋+CAS
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
-
putIntVolatile:
putInt
的Volatile版本-
public native void putIntVolatile(Object o, long offset, int x);
-
-
putOrderedInt:
putIntVolatile
的有序\惰性版本-
public native void putOrderedInt(Object o, long offset, int x);
-
Memory操作內存
直接訪問內存的方法。
-
allocateMemory:分配內存空間,並返回偏移量
-
public native long allocateMemory(long bytes);
-
-
copyMemory:複製內存空間
-
// @since 1.7 public native void copyMemory(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes); public void copyMemory(long srcAddress, long destAddress, long bytes) { copyMemory(null, srcAddress, null, destAddress, bytes); }
-
-
freeMemory:釋放內存空間
-
public native void freeMemory(long address);
-
-
getAddress: 獲得內存地址,無符號整數long
-
public native long getAddress(long address);
-
-
getInt:獲得指定偏移量的變量值
-
public native int getInt(Object o, long offset);
-
-
putInt:可以修改變量的值
-
/** 參數一:對象 參數二:字段偏移量 參數三:要設置的值 */ public native void putInt(Object o, long offset, int x);
-
示例:
//通過魔法類修改對象實例的屬性值
UnsafeDemo unsafeDemo = new UnsafeDemo();
long ageOffset =
unsafe.objectFieldOffset(UnsafeDemo.class.getDeclaredField("age"));
unsafe.putInt(unsafeDemo, ageOffset, 30); // 將字段age的值改爲30
Unsafe實現CAS
案例:AtomicInteger的原子操作
示例AtomicInteger#incrementAndGet方法的實現邏輯:
java.util.concurrent.atomic.AtomicInteger#incrementAndGet
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
sun.misc.Unsafe#getAndAddInt
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
// 自旋+CAS
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
這裏分享的是常見的一些API,Unsafe還有很多其他類似的方法,不再一一列舉。