併發做爲 Java 中很是重要的一部分,其內部大量使用了 Unsafe 類,它爲 java.util.concurrent 包中的類提供了底層支持。然而 Unsafe 並非 JDK 的標準,它是 Sun 的內部實現,存在於 sun.misc 包中,在 Oracle 發行的 JDK 中並不包含其源代碼。java
Unsafe 提供兩個功能:面試
雖然咱們在通常的併發編程中不會直接用到 Unsafe,可是不少 Java 基礎類庫與諸如 Netty、Cassandra 和 Kafka 等高性能庫都採用它,它在提高 Java 運行效率、加強 Java 語言底層操做能力方面起了很大做用。筆者以爲了解一個使用如此普遍的庫仍是頗有必要的。本文將深刻到 Unsafe 的源碼,分析一下它的邏輯。編程
本文使用 OpenJDK(jdk8-b120)中 Unsafe 的源碼,Unsafe 的實現是和虛擬機實現相關的,不一樣的虛擬機實現,它們的對象結構可能不同,這個 Unsafe 只能用於 Hotspot 虛擬機。bootstrap
源碼查看:http://hg.openjdk.java.net/jdk/jdk/file/a1ee9743f4ee/jdk/src/share/classes/sun/misc/Unsafe.java數組
Unsafe 爲調用者提供執行非安全操做的能力,因爲返回的 Unsafe 對象能夠讀寫任意的內存地址數據,調用者應該當心謹慎的使用改對象,必定不用把它傳遞到非信任代碼。該類的大部分方法都是很是底層的操做,並牽涉到一小部分典型的機器都包含的硬件指令,編譯器能夠對這些進行優化。緩存
public final class Unsafe { private static native void registerNatives(); static { registerNatives(); sun.reflect.Reflection.registerMethodsToFilter(Unsafe.class, "getUnsafe"); } private Unsafe() {} private static final Unsafe theUnsafe = new Unsafe(); @CallerSensitive public static Unsafe getUnsafe() { Class<?> caller = Reflection.getCallerClass(); if (!VM.isSystemDomainLoader(caller.getClassLoader())) throw new SecurityException("Unsafe"); return theUnsafe; } ...... }
上面的代碼包含以下功能:安全
要使用此類必須得到其實例,得到實例的方法是 getUnsafe(),那就先看看這個方法。cookie
getUnsafe() 方法包含一個註釋 @CallerSensitive,說明該方法不是誰均可以調用的。若是調用者不是由系統類加載器(bootstrap classloader)加載,則將拋出 SecurityException,因此默認狀況下,應用代碼調用此方法將拋出異常。咱們的代碼要想經過 getUnsafe() 獲取實例是不可能的了,不過可經過反射獲取 Unsafe 實例:併發
Field f= Unsafe.class.getDeclaredField("theUnsafe"); f.setAccessible(true); U= (Unsafe) f.get(null);
此處經過反射獲取類的靜態字段,這樣就繞過了 getUnsafe() 的安全限制。dom
也能夠經過反射獲取構造方法再實例化,但這樣違法了該類單例的原則,而且在使用上可能會有其它問題,因此不建議這樣作。
再來看看如何獲取指定變量的值:
public native int getInt(Object o, long offset);
獲取指定對象中指定偏移量的字段或數組元素的值,參數 o 是變量關聯的 Java 堆對象,若是指定的對象爲 null,則獲取內存中該地址的值(即 offset 爲內存絕對地址)。
若是不符合如下任意條件,則該方法返回的值是不肯定的:
若是以上任意條件符合,則調用者能獲取 Java 字段的引用,可是若是該字段的類型和該方法返回的類型不一致,則結果是不必定的,好比該字段是 short,但調用了 getInt 方法。
該方法經過兩個參數引用一個變量,它爲 Java 變量提供 double-register 地址模型。若是引用的對象爲 null,則該方法將 offset 看成內存絕對地址,就像 getInt(long)同樣,它爲非 Java 變量提供 single-register 地址模型,然而 Java 變量的內存佈局可能和非 Java 變量的內存佈局不一樣,不該該假設這兩種地址模型是相等的。同時,應該記住 double-register 地址模型的偏移量不該該和 single-register 地址模型中的地址(long 參數)混淆。
再看條件中提到的幾個相關方法:
public native long objectFieldOffset(Field f); public native Object staticFieldBase(Field f); public native long staticFieldOffset(Field f); public native int arrayBaseOffset(Class arrayClass); public native int arrayIndexScale(Class arrayClass);
這幾個方法分別是獲取靜態字段、非靜態字段與數組字段的一些信息。
objectFieldOffset
很難想象 JVM 須要使用這麼多比特位來編碼非數組對象的偏移量,爲了和該類的其它方法保持一致,因此該方法也返回 long 類型。
staticFieldBase
獲取指定靜態字段的位置,和 staticFieldOffset 一塊兒使用。獲取該靜態字段所在的「對象」可經過相似 getInt(Object,long)的方法訪問。
staticFieldOffset
返回給定的字段在該類的偏移地址。對於任何給定的字段,該方法老是返回相同的值,同一個類的不一樣字段老是返回不一樣的值。從 1.4.1 開始,字段的偏移以 long 表示,雖然 Sun 的 JVM 只使用了 32 位,可是那些將靜態字段存儲到絕對地址的 JVM 實現須要使用 long 類型的偏移量,經過 getXX(null,long) 獲取字段值,爲了保持代碼遷移到 64 位平臺上 JVM 的優良性,必須保持靜態字段偏移量的全部比。
arrayBaseOffset
返回給定數組類第一個元素在內存中的偏移量。若是 arrayIndexScale 方法返回非 0 值,要得到訪問數組元素的新的偏移量,則須要使用 s。
arrayIndexScale
返回給定數組類的每一個元素在內存中的 scale(所佔用的字節)。然而對於「narrow」類型的數組,相似 getByte(Object, int)的訪問方法通常不會得到正確的結果,因此這些類返回的 scale 會是 0。
下邊用代碼解釋:
public class MyObj { int objField=10; static int clsField=10; int[] array={10,20,30,40,50}; static Unsafe U; static { try { init(); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } public static void init() throws NoSuchFieldException, IllegalAccessException { Field f= Unsafe.class.getDeclaredField("theUnsafe"); f.setAccessible(true); U= (Unsafe) f.get(null); } ...... }
定義一個類包含成員變量 objField、類變量 clsField、成員數組 array 用於實驗。要獲取正確的結果,必須知足註釋裏的三個條件之一:
一、offset 經過 objectFieldOffset 方法獲取的類的某一字段的偏移量,而且關聯的對象 o 是該類的兼容對象(對象 o 所在的類必須是該類或該類的子類)
public class MyObjChild extends MyObj { int anthor; }
static void getObjFieldVal() throws NoSuchFieldException { Field field=MyObj.class.getDeclaredField("objField"); long offset= U.objectFieldOffset(field); MyObj obj=new MyObj(); int val= U.getInt(obj,offset); System.out.println("1.\t"+(val==10)); MyObjChild child=new MyObjChild(); int corVal1= U.getInt(child,offset); System.out.println("2.\t"+(corVal1==10)); Field fieldChild=MyObj.class.getDeclaredField("objField"); long offsetChild= U.objectFieldOffset(fieldChild); System.out.println("3.\t"+(offset==offsetChild)); int corVal2= U.getInt(obj,offsetChild); System.out.println("4.\t"+(corVal2==10)); short errVal1=U.getShort(obj,offset); System.out.println("5.\t"+(errVal1==10)); int errVal2=U.getInt("abcd",offset); System.out.println("6.\t"+errVal2); }
輸出結果爲:
true true true true true -223271518
這裏重點說一下第五個,objField 是一個 int 類型,佔四個字節,其值爲 10,二進制爲 00000000 00000000 00000000 00001010。Intel 處理器讀取內存使用的是小端(Little-Endian)模式,在使用 Intel 處理器的機器的內存中多字節類型是按小端存儲的,即低位在內存的低字節存儲,高位在內存的高字節存儲,因此 int 10 在內存中是(offset 0-3) 00001010 00000000 00000000 00000000。使用 getShort 會讀取兩個字節,即 00001010 00000000,獲取的值仍爲 10。
可是某些處理器是使用大端(Big-Endian),如 ARM 支持小端和大端,使用此處理器的機器的內存就會按大端存儲多字節類型,與小端相反,此模式下低位在內存的高字節存儲,高位在內存的低字節存儲,因此 int 10 在內存中是(offset 0-3)00000000 00000000 00000000 00001010。在這種狀況下,getShort 獲取的值將會是 0。
不一樣的機器可能產生不同的結果,基於此狀況,若是字段是 int 類型,但須要一個 short 類型,也不該該調用 getShort,而應該調用 getInt,而後強制轉換成 short。此外,若是調用 getLong,該方法返回的值必定不是 10。就像方法註釋所說,調用該類型方法時,要保證方法的返回值和字段的值是同一種類型。
第五個測試獲取非 MyObj 實例的偏移位置的值,這種狀況下代碼自己並不會報錯,但獲取到的值並不是該字段的值(未定義的值)
二、offset 經過 staticFieldOffset 方法獲取的類的某一字段的偏移量,o 是經過 staticFieldBase 方法獲取的對象
static void getClsFieldVal() throws NoSuchFieldException { Field field=MyObj.class.getDeclaredField("clsField"); long offset= U.staticFieldOffset(field); Object obj=U.staticFieldBase(field); int val1=U.getInt(MyObj.class,offset); System.out.println("1.\t"+(val1==10)); int val2=U.getInt(obj,offset); System.out.println("2.\t"+(val2==10)); }
輸出結果:
true true
獲取靜態字段的值,有兩個方法:staticFieldBase 獲取字段所在的對象,靜態字段附着於 Class 自己(java.lang.Class 的實例),該方法返回的其實就是該類自己,本例中是 MyObj.class。
三、若是 o 引用的是數組,則 offset 的值爲 B+N*S,其中 N 是數組的合法的下標,B 是經過 arrayBaseOffset 方法從該數組類獲取的,S 是經過 arrayIndexScale 方法從該數組類獲取的
static void getArrayVal(int index,int expectedVal) throws NoSuchFieldException { int base=U.arrayBaseOffset(int[].class); int scale=U.arrayIndexScale(int[].class); MyObj obj=new MyObj(); Field field=MyObj.class.getDeclaredField("array"); long offset= U.objectFieldOffset(field); int[] array= (int[]) U.getObject(obj,offset); int val1=U.getInt(array,(long)base+index*scale); System.out.println("1.\t"+(val1==expectedVal)); int val2=U.getInt(obj.array,(long)base+index*scale); System.out.println("2.\t"+(val2==expectedVal)); }
getArrayVal(2,30);
輸出結果:
true true
獲取數組的值以及獲取數組中某下標的值。獲取數組某一下標的偏移量有一個計算公式 B+N*S,B 是數組元素在數組中的基準偏移量,S 是每一個元素佔用的字節數,N 是數組元素的下標。
有個要注意的地方,上面例子中方法內的數組的 offset 和 base 是兩個徹底不一樣的偏移量,offset 是數組 array 在對象 obj 中的偏移量,base 是數組元素在數組中的基準偏移量,這兩個值沒有任何聯繫,不能經過 offset 推導出 base。
getInt 的參數 o 能夠是 null,在這種狀況下,其和方法 getInt(long) 就是同樣的了,offset 就不是表示相對的偏移地址了,而是表示內存中的絕對地址。操做系統中,一個進程是不能訪問其餘進程的內存的,因此傳入 getInt 中的絕對地址必須是當前 JVM 管理的內存地址,不然進程會退出。
下一個方法,將值存儲到 Java 變量中:
public native void putInt(Object o, long offset, int x);
修改指定位置的內存,測試代碼:
static void setObjFieldVal(int val) throws NoSuchFieldException { Field field=MyObj.class.getDeclaredField("objField"); long offset= U.objectFieldOffset(field); MyObj obj=new MyObj(); U.putInt(obj,offset,val); int getVal= U.getInt(obj,offset); System.out.println(val==getVal); U.putLong(obj,offset,val); Field fieldArray=MyObj.class.getDeclaredField("array"); long offsetArray= U.objectFieldOffset(fieldArray); // int[] array= (int[]) U.getObject(obj,offsetArray); // for(int i=0;i<array.length;i++){ // System.out.println(array[i]); // } }
objField 是 int 類型,經過 putInt 修改 objField 的值能夠正常修改,結果打印 true。而後使用 putLong 修改 objField 的值,這個修改操做自己也不會報錯,可是 objField 並非 long 類型,這樣修改會致使其它程序錯誤,它不只修改了 objField 的內存值,還修改了 objField 以後四個字節的內存值。
在這個例子中,objField 後面八個字節存儲的是 array 字段所表示的數組對象的偏移位置,可是被修改了,若是後面的代碼嘗試訪問 array 字段就會出錯。修改其它字段(array、clsField)也是同樣的,只要按以前的方法獲取字段的偏移位置,使用與字段類型一致的 put 方法就能夠。
下面的方法都是差很少的,只是針對不一樣的數據類型或者兼容 1.4 的字節碼:
public native void putObject(Object o, long offset, Object x); public native boolean getBoolean(Object o, long offset); public native void putBoolean(Object o, long offset, boolean x); public native byte getByte(Object o, long offset); public native void putByte(Object o, long offset, byte x); public native short getShort(Object o, long offset); public native void putShort(Object o, long offset, short x); public native char getChar(Object o, long offset); public native void putChar(Object o, long offset, char x); public native long getLong(Object o, long offset); public native void putLong(Object o, long offset, long x); public native float getFloat(Object o, long offset); public native void putFloat(Object o, long offset, float x); public native double getDouble(Object o, long offset); public native void putDouble(Object o, long offset, double x); @Deprecated public int getInt(Object o, int offset) { return getInt(o, (long)offset); } @Deprecated public void putInt(Object o, int offset, int x) { putInt(o, (long)offset, x); } @Deprecated public Object getObject(Object o, int offset) { return getObject(o, (long)offset); } @Deprecated public void putObject(Object o, int offset, Object x) { putObject(o, (long)offset, x); } @Deprecated public boolean getBoolean(Object o, int offset) { return getBoolean(o, (long)offset); } @Deprecated public void putBoolean(Object o, int offset, boolean x) { putBoolean(o, (long)offset, x); } @Deprecated public byte getByte(Object o, int offset) { return getByte(o, (long)offset); } @Deprecated public void putByte(Object o, int offset, byte x) { putByte(o, (long)offset, x); } @Deprecated public short getShort(Object o, int offset) { return getShort(o, (long)offset); } @Deprecated public void putShort(Object o, int offset, short x) { putShort(o, (long)offset, x); } @Deprecated public char getChar(Object o, int offset) { return getChar(o, (long)offset); } @Deprecated public void putChar(Object o, int offset, char x) { putChar(o, (long)offset, x); } @Deprecated public long getLong(Object o, int offset) { return getLong(o, (long)offset); } @Deprecated public void putLong(Object o, int offset, long x) { putLong(o, (long)offset, x); } @Deprecated public float getFloat(Object o, int offset) { return getFloat(o, (long)offset); } @Deprecated public void putFloat(Object o, int offset, float x) { putFloat(o, (long)offset, x); } @Deprecated public double getDouble(Object o, int offset) { return getDouble(o, (long)offset); } @Deprecated public void putDouble(Object o, int offset, double x) { putDouble(o, (long)offset, x); }
下面的方法和上面的也相似,只是這些方法只有一個參數,即內存絕對地址,這些方法不須要 Java 對象地址做爲基準地址,因此它們能夠做用於本地方法區:
//獲取內存地址的值,若是地址是 0 或者不是指向經過 allocateMemory 方法獲取的內存塊,則結果是未知的 public native byte getByte(long address); //將一個值寫入內存,若是地址是 0 或者不是指向經過 allocateMemory 方法獲取的內存塊,則結果是未知的 public native void putByte(long address, byte x); public native short getShort(long address); public native void putShort(long address, short x); public native char getChar(long address); public native void putChar(long address, char x); public native int getInt(long address); public native void putInt(long address, int x); public native long getLong(long address); public native void putLong(long address, long x); public native float getFloat(long address); public native void putFloat(long address, float x); public native double getDouble(long address); public native void putDouble(long address, double x);
這裏提到一個方法 allocateMemory,它是用於分配本地內存的。看看和本地內存有關的幾個方法:
///包裝malloc,realloc,free /** * 分配指定大小的一塊本地內存。分配的這塊內存不會初始化,它們的內容一般是沒用的數據 * 返回的本地指針不會是 0,而且該內存塊是連續的。調用 freeMemory 方法能夠釋放此內存,調用 * reallocateMemory 方法能夠從新分配 */ public native long allocateMemory(long bytes); /** * 從新分配一塊指定大小的本地內存,超出老內存塊的字節不會被初始化,它們的內容一般是沒用的數據 * 當且僅當請求的大小爲 0 時,該方法返回的本地指針會是 0。 * 該內存塊是連續的。調用 freeMemory 方法能夠釋放此內存,調用 reallocateMemory 方法能夠從新分配 * 參數 address 能夠是 null,這種狀況下會分配新內存(和 allocateMemory 同樣) */ public native long reallocateMemory(long address, long bytes); /** * 將給定的內存塊的全部字節設置成固定的值(一般是 0) * 該方法經過兩個參數肯定內存塊的基準地址,就像在 getInt(Object,long) 中討論的,它提供了 double-register 地址模型 * 若是引用的對象是 null, 則 offset 會被當成絕對基準地址 * 該寫入操做是按單元寫入的,單元的字節大小由地址和長度參數決定,每一個單元的寫入是原子性的。若是地址和長度都是 8 的倍數,則一個單元爲 long * 型(一個單元 8 個字節);若是地址和長度都是 4 的倍數,則一個單元爲 int 型(一個單元 4 個字節); * 若是地址和長度都是 2 的倍數,則一個單元爲 short 型(一個單元 2 個字節); */ public native void setMemory(Object o, long offset, long bytes, byte value); //將給定的內存塊的全部字節設置成固定的值(一般是 0) //就像在 getInt(Object,long) 中討論的,該方法提供 single-register 地址模型 public void setMemory(long address, long bytes, byte value) { setMemory(null, address, bytes, value); } //複製指定內存塊的字節到另外一內存塊 //該方法的兩個基準地址分別由兩個參數決定 public native void copyMemory(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes); //複製指定內存塊的字節到另外一內存塊,但使用 single-register 地址模型 public void copyMemory(long srcAddress, long destAddress, long bytes) { copyMemory(null, srcAddress, null, destAddress, bytes); } //釋放經過 allocateMemory 或者 reallocateMemory 獲取的內存,若是參數 address 是 null,則不作任何處理 public native void freeMemory(long address);
allocateMemory、reallocateMemory、freeMemory 與 setMemory 分別是對 C 函數 malloc、realloc、free 和 memset 的封裝,這樣該類就提供了動態獲取/釋放本地方法區內存的功能。
來一個簡單的例子,申請內存-寫入內存-讀取內存-釋放內存:
long address=U.allocateMemory(10); U.setMemory(address,10,(byte)1); /** * 1的二進制碼爲00000001,int爲四個字節,U.getInt將讀取四個字節, * 讀取的字節爲00000001 00000001 00000001 00000001 */ int i=0b00000001000000010000000100000001; System.out.println(i==U.getInt(address)); U.freeMemory(address);
接下來看看獲取類變量相關信息的幾個方法:
/// random queries /// 隨機搜索,對象是放在一塊連續的內存空間中,因此是支持隨機搜索的 /** * staticFieldOffset,objectFieldOffset,arrayBaseOffset 方法的返回值不會是該常量(-1) */ public static final int INVALID_FIELD_OFFSET = -1; /** * 返回字段的偏移量,32 字節 * 從 1.4.1 開始,對於靜態字段,請使用 staticFieldOffset 方法,非靜態字段使用 objectFieldOffset 方法獲取 */ @Deprecated public int fieldOffset(Field f) { if (Modifier.isStatic(f.getModifiers())) return (int) staticFieldOffset(f); else return (int) objectFieldOffset(f); } /** * 返回用於訪問靜態字段的基準地址 * 從 1.4.1 開始,要獲取訪問指定字段的基準地址,請使用 staticFieldBase(Field) * 該方法僅能做用於把全部靜態字段放在一塊兒的 JVM 實現 */ @Deprecated public Object staticFieldBase(Class<?> c) { Field[] fields = c.getDeclaredFields(); for (int i = 0; i < fields.length; i++) { if (Modifier.isStatic(fields[i].getModifiers())) { return staticFieldBase(fields[i]); } } return null; } /** * 返回給定的字段在該類的偏移地址 * * 對於任何給定的字段,該方法老是返回相同的值;同一個類的不一樣字段老是返回不一樣的值 * * 從 1.4.1 開始,字段的偏移以 long 表示,雖然 Sun 的 JVM 只使用了 32 位,可是那些將靜態字段存儲到絕對地址的 JVM 實現 * 須要使用 long 類型的偏移量,經過 getXX(null,long) 獲取字段值,爲了保持代碼遷移到 64 位平臺上 JVM 的優良性, * 必須保持靜態字段偏移量的全部比特位 */ public native long staticFieldOffset(Field f); /** * 很難想象 JVM 須要使用這麼多比特位來編碼非數組對象的偏移量,它們只須要不多的比特位就能夠了(有誰看過有100個成員變量的類麼? * 一個字節能表示 256 個成員變量), * 爲了和該類的其餘方法保持一致,因此該方法也返回 long 類型 * */ public native long objectFieldOffset(Field f); /** * 獲取指定靜態字段的位置,和 staticFieldOffset 一塊兒使用 * 獲取該靜態字段所在的"對象",這個"對象"可經過相似 getInt(Object,long) 的方法訪問 * 該"對象"多是 null,而且引用的多是對象的"cookie"(此處cookie具體含義未知,沒有找到相關資料),不保證是真正的對象,該"對象"只能看成此類中 put 和 get 方法的參數, * 其餘狀況下不該該使用它 */ public native Object staticFieldBase(Field f); /** * 檢查給定的類是否須要初始化,它一般和 staticFieldBase 方法一塊兒使用 * 只有當 ensureClassInitialized 方法不產生任何影響時纔會返回 false */ public native boolean shouldBeInitialized(Class<?> c); /** * 確保給定的類已被初始化,它一般和 staticFieldBase 方法一塊兒使用 */ public native void ensureClassInitialized(Class<?> c); /** * 返回給定數組類第一個元素在內存中的偏移量,若是 arrayIndexScale 方法返回非0值,要得到訪問數組元素的新的偏移量, * 須要使用 scale */ public native int arrayBaseOffset(Class<?> arrayClass); /** The value of {@code arrayBaseOffset(boolean[].class)} */ public static final int ARRAY_BOOLEAN_BASE_OFFSET = theUnsafe.arrayBaseOffset(boolean[].class); /** The value of {@code arrayBaseOffset(byte[].class)} */ public static final int ARRAY_BYTE_BASE_OFFSET = theUnsafe.arrayBaseOffset(byte[].class); /** The value of {@code arrayBaseOffset(short[].class)} */ public static final int ARRAY_SHORT_BASE_OFFSET = theUnsafe.arrayBaseOffset(short[].class); /** The value of {@code arrayBaseOffset(char[].class)} */ public static final int ARRAY_CHAR_BASE_OFFSET = theUnsafe.arrayBaseOffset(char[].class); /** The value of {@code arrayBaseOffset(int[].class)} */ public static final int ARRAY_INT_BASE_OFFSET = theUnsafe.arrayBaseOffset(int[].class); /** The value of {@code arrayBaseOffset(long[].class)} */ public static final int ARRAY_LONG_BASE_OFFSET = theUnsafe.arrayBaseOffset(long[].class); /** The value of {@code arrayBaseOffset(float[].class)} */ public static final int ARRAY_FLOAT_BASE_OFFSET = theUnsafe.arrayBaseOffset(float[].class); /** The value of {@code arrayBaseOffset(double[].class)} */ public static final int ARRAY_DOUBLE_BASE_OFFSET = theUnsafe.arrayBaseOffset(double[].class); /** The value of {@code arrayBaseOffset(Object[].class)} */ public static final int ARRAY_OBJECT_BASE_OFFSET = theUnsafe.arrayBaseOffset(Object[].class); /** * 返回給定數組類的每一個元素在內存中的 scale(所佔用的字節)。然而對於"narrow"類型的數組,相似 getByte(Object, int) 的訪問方法 * 通常不會得到正確的結果,因此這些類返回的 scale 會是 0 * (本人水平有限,此處narrow類型不知道具體含義,不瞭解何時此方法會返回0) */ public native int arrayIndexScale(Class<?> arrayClass); /** The value of {@code arrayIndexScale(boolean[].class)} */ public static final int ARRAY_BOOLEAN_INDEX_SCALE = theUnsafe.arrayIndexScale(boolean[].class); /** The value of {@code arrayIndexScale(byte[].class)} */ public static final int ARRAY_BYTE_INDEX_SCALE = theUnsafe.arrayIndexScale(byte[].class); /** The value of {@code arrayIndexScale(short[].class)} */ public static final int ARRAY_SHORT_INDEX_SCALE = theUnsafe.arrayIndexScale(short[].class); /** The value of {@code arrayIndexScale(char[].class)} */ public static final int ARRAY_CHAR_INDEX_SCALE = theUnsafe.arrayIndexScale(char[].class); /** The value of {@code arrayIndexScale(int[].class)} */ public static final int ARRAY_INT_INDEX_SCALE = theUnsafe.arrayIndexScale(int[].class); /** The value of {@code arrayIndexScale(long[].class)} */ public static final int ARRAY_LONG_INDEX_SCALE = theUnsafe.arrayIndexScale(long[].class); /** The value of {@code arrayIndexScale(float[].class)} */ public static final int ARRAY_FLOAT_INDEX_SCALE = theUnsafe.arrayIndexScale(float[].class); /** The value of {@code arrayIndexScale(double[].class)} */ public static final int ARRAY_DOUBLE_INDEX_SCALE = theUnsafe.arrayIndexScale(double[].class); /** The value of {@code arrayIndexScale(Object[].class)} */ public static final int ARRAY_OBJECT_INDEX_SCALE = theUnsafe.arrayIndexScale(Object[].class);
上面的一些方法以前已經提到過,註釋也說的比較明白, 說一下 shouldBeInitialized 和 ensureClassInitialized,shouldBeInitialized 判斷類是否已初始化,ensureClassInitialized 執行初始化。有個概念須要瞭解,虛擬機加載類包括加載和連接階段,加載階段只是把類加載進內存,連接階段會驗證加載的代碼的合法性,並初始化靜態字段和靜態塊;shouldBeInitialized 就是檢查連接階段有沒有執行。
static void clsInitialized() throws NoSuchFieldException { System.out.println(U.shouldBeInitialized(MyObj.class)); System.out.println(U.shouldBeInitialized(MyObjChild.class)); U.ensureClassInitialized(MyObjChild.class); System.out.println(U.shouldBeInitialized(MyObjChild.class)); }
public class MyObjChild extends MyObj { static int f1=1; final static int f2=1; static { f1=2; System.out.println("MyObjChild init"); } }
輸出:
false true MyObjChild init false
第一行輸出 false 是由於我這個代碼(包括 main 方法)是在 MyObj 類裏寫的,執行 main 的時候,MyObj 已經加載並初始化了。調用 U.shouldBeInitialized(MyObjChild.class) 只會加載 MyObjChild.class,但不會初始化,執行 ensureClassInitialized 纔會初始化。
static void clsInitialized2() throws NoSuchFieldException { Field f1=MyObjChild.class.getDeclaredField("f1"); Field f2=MyObjChild.class.getDeclaredField("f2"); long f1Offset= U.staticFieldOffset(f1); long f2Offset= U.staticFieldOffset(f2); int f1Val=U.getInt(MyObjChild.class,f1Offset); int f2Val=U.getInt(MyObjChild.class,f2Offset); System.out.println("1.\t"+(f1Val==0)); System.out.println("2.\t"+(f2Val==1)); U.ensureClassInitialized(MyObjChild.class); f1Val=U.getInt(MyObjChild.class,f1Offset); System.out.println("3.\t"+(f1Val==2)); }
輸出:
1.true 2.true MyObjChild init 3.true
f1 是 static int,f2 是 final static int,由於 f2 是 final,它的值在編譯期就決定了,存放在類的常量表裏,因此即便尚未初始化它的值就是 1。
/** * 獲取本地指針所佔用的字節大小,值爲 4 或者 8。其餘基本類型的大小由其內容決定 */ public native int addressSize(); /** The value of {@code addressSize()} */ public static final int ADDRESS_SIZE = theUnsafe.addressSize(); /** * 本地內存頁大小,值爲 2 的 N 次方 */ public native int pageSize();
addressSize 返回指針的大小,32 位虛擬機返回 4,64 位虛擬機默認返回 8,開啓指針壓縮功能(-XX:-UseCompressedOops)則返回 4。基本類型不是用指針表示的,它是直接存儲的值。通常狀況下,咱們會說在 Java 中,基本類型是值傳遞,對象是引用傳遞。Java 官方的表述是在任何狀況下 Java 都是值傳遞。基本類型是傳遞值自己,對象類型是傳遞指針的值。
/// random trusted operations from JNI: /// JNI信任的操做 /** * 告訴虛擬機定義一個類,加載類不作安全檢查,默認狀況下,參數類加載器(ClassLoader)和保護域(ProtectionDomain)來自調用者類 */ public native Class<?> defineClass(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain); /** * 定義一個匿名類,這裏說的和咱們代碼裏寫的匿名內部類不是一個東西。 * (能夠參考知乎上的一個問答 https://www.zhihu.com/question/51132462) */ public native Class<?> defineAnonymousClass(Class<?> hostClass, byte[] data, Object[] cpPatches); /** * 分配實例的內存空間,但不會執行構造函數。若是沒有執行初始化,則會執行初始化 */ public native Object allocateInstance(Class<?> cls) throws InstantiationException; /** Lock the object. It must get unlocked via {@link #monitorExit}. * * 獲取對象內置鎖(即 synchronized 關鍵字獲取的鎖),必須經過 monitorExit 方法釋放鎖 * (synchronized 代碼塊在編譯後會產生兩個指令:monitorenter,monitorexit) */ public native void monitorEnter(Object o); /** * Unlock the object. It must have been locked via {@link * #monitorEnter}. * 釋放鎖 */ public native void monitorExit(Object o); /** * 嘗試獲取對象內置鎖,經過返回 true 和 false 表示是否成功獲取鎖 */ public native boolean tryMonitorEnter(Object o); /** Throw the exception without telling the verifier. * 不通知驗證器(verifier)直接拋出異常(此處 verifier 具體含義未知,沒有找到相關資料) */ public native void throwException(Throwable ee);
allocateInstance 方法的測試
public class MyObjChild extends MyObj { static int f1=1; int f2=1; static { f1=2; System.out.println("MyObjChild init"); } public MyObjChild(){ f2=2; System.out.println("run construct"); } }
static void clsInitialized3() throws InstantiationException { MyObjChild myObj= (MyObjChild) U.allocateInstance(MyObjChild.class); System.out.println("1.\t"+(MyObjChild.f1==2)); System.out.println("1.\t"+(myObj.f2==0)); }
輸出:
MyObjChild init 1.true 2.true
能夠看到分配對象的時候只執行了類的初始化代碼,沒有執行構造函數。
來看看最重要的 CAS 方法
/** * Atomically update Java variable to <tt>x</tt> if it is currently * holding <tt>expected</tt>. * @return <tt>true</tt> if successful * * 若是變量的值爲預期值,則更新變量的值,該操做爲原子操做 * 若是修改爲功則返回true */ public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object x); /** * Atomically update Java variable to <tt>x</tt> if it is currently * holding <tt>expected</tt>. * @return <tt>true</tt> if successful */ public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x); /** * Atomically update Java variable to <tt>x</tt> if it is currently * holding <tt>expected</tt>. * @return <tt>true</tt> if successful */ public final native boolean compareAndSwapLong(Object o, long offset, long expected, long x);
這幾個方法應該是最經常使用的方法了,用於實現原子性的 CAS 操做,這些操做能夠避免加鎖,通常狀況下,性能會更好, java.util.concurrent 包下不少類就是用的這些 CAS 操做而沒有用鎖。
static void cas() throws NoSuchFieldException { Field field=MyObj.class.getDeclaredField("objField"); long offset= U.objectFieldOffset(field); MyObj myObj=new MyObj(); myObj.objField=1; U.compareAndSwapInt(myObj,offset,0,2); System.out.println("1.\t"+(myObj.objField==2)); U.compareAndSwapInt(myObj,offset,1,2); System.out.println("2.\t"+(myObj.objField==2)); }
輸出:
1.false 2.true
/** * 獲取給定變量的引用值,該操做有 volatile 加載語意,其餘方面和 getObject(Object, long) 同樣 */ public native Object getObjectVolatile(Object o, long offset); /** * 將引用值寫入給定的變量,該操做有 volatile 加載語意,其餘方面和 putObject(Object, long, Object) 同樣 */ public native void putObjectVolatile(Object o, long offset, Object x); /** Volatile version of {@link #getInt(Object, long)} */ public native int getIntVolatile(Object o, long offset); /** Volatile version of {@link #putInt(Object, long, int)} */ public native void putIntVolatile(Object o, long offset, int x); /** Volatile version of {@link #getBoolean(Object, long)} */ public native boolean getBooleanVolatile(Object o, long offset); /** Volatile version of {@link #putBoolean(Object, long, boolean)} */ public native void putBooleanVolatile(Object o, long offset, boolean x); /** Volatile version of {@link #getByte(Object, long)} */ public native byte getByteVolatile(Object o, long offset); /** Volatile version of {@link #putByte(Object, long, byte)} */ public native void putByteVolatile(Object o, long offset, byte x); /** Volatile version of {@link #getShort(Object, long)} */ public native short getShortVolatile(Object o, long offset); /** Volatile version of {@link #putShort(Object, long, short)} */ public native void putShortVolatile(Object o, long offset, short x); /** Volatile version of {@link #getChar(Object, long)} */ public native char getCharVolatile(Object o, long offset); /** Volatile version of {@link #putChar(Object, long, char)} */ public native void putCharVolatile(Object o, long offset, char x); /** Volatile version of {@link #getLong(Object, long)} */ public native long getLongVolatile(Object o, long offset); /** Volatile version of {@link #putLong(Object, long, long)} */ public native void putLongVolatile(Object o, long offset, long x); /** Volatile version of {@link #getFloat(Object, long)} */ public native float getFloatVolatile(Object o, long offset); /** Volatile version of {@link #putFloat(Object, long, float)} */ public native void putFloatVolatile(Object o, long offset, float x); /** Volatile version of {@link #getDouble(Object, long)} */ public native double getDoubleVolatile(Object o, long offset); /** Volatile version of {@link #putDouble(Object, long, double)} */ public native void putDoubleVolatile(Object o, long offset, double x);
這是具備 volatile 語意的 get 和 put方法。volatile 語意爲保證不一樣線程之間的可見行,即一個線程修改一個變量以後,保證另外一線程能觀測到此修改。這些方法可使非 volatile 變量具備 volatile 語意。
/** * putObjectVolatile(Object, long, Object)的另外一個版本(有序的/延遲的),它不保證其餘線程能當即看到修改, * 該方法一般只對底層爲 volatile 的變量(或者 volatile 類型的數組元素)有幫助 */ public native void putOrderedObject(Object o, long offset, Object x); /** Ordered/Lazy version of {@link #putIntVolatile(Object, long, int)} */ public native void putOrderedInt(Object o, long offset, int x); /** Ordered/Lazy version of {@link #putLongVolatile(Object, long, long)} */ public native void putOrderedLong(Object o, long offset, long x);
有三類很相近的方法:putXx、putXxVolatile 與 putOrderedXx:
/** * 釋放當前阻塞的線程。若是當前線程沒有阻塞,則下一次調用 park 不會阻塞。這個操做是"非安全"的 * 是由於調用者必須經過某種方式保證該線程沒有被銷燬 * */ public native void unpark(Object thread); /** * 阻塞當前線程,當發生以下狀況時返回: * 一、調用 unpark 方法 * 二、線程被中斷 * 三、時間過時 * 四、spuriously * 該操做放在 Unsafe 類裏沒有其它意義,它能夠放在其它的任何地方 */ public native void park(boolean isAbsolute, long time);
阻塞和釋放當前線程,java.util.concurrent 中的鎖就是經過這兩個方法實現線程阻塞和釋放的。
/** *獲取一段時間內,運行的任務隊列分配到可用處理器的平均數(日常說的 CPU 使用率) * */ public native int getLoadAverage(double[] loadavg, int nelems);
統計 CPU 負載。
// The following contain CAS-based Java implementations used on // platforms not supporting native instructions //下面的方法包含基於 CAS 的 Java 實現,用於不支持本地指令的平臺 /** * 在給定的字段或數組元素的當前值原子性的增長給定的值 * @param o 字段/元素所在的對象/數組 * @param offset 字段/元素的偏移 * @param delta 須要增長的值 * @return 原值 * @since 1.8 */ public final int getAndAddInt(Object o, long offset, int delta) { int v; do { v = getIntVolatile(o, offset); } while (!compareAndSwapInt(o, offset, v, v + delta)); return v; } public final long getAndAddLong(Object o, long offset, long delta) { long v; do { v = getLongVolatile(o, offset); } while (!compareAndSwapLong(o, offset, v, v + delta)); return v; } /** * 將給定的字段或數組元素的當前值原子性的替換給定的值 * @param o 字段/元素所在的對象/數組 * @param offset field/element offset * @param newValue 新值 * @return 原值 * @since 1.8 */ public final int getAndSetInt(Object o, long offset, int newValue) { int v; do { v = getIntVolatile(o, offset); } while (!compareAndSwapInt(o, offset, v, newValue)); return v; } public final long getAndSetLong(Object o, long offset, long newValue) { long v; do { v = getLongVolatile(o, offset); } while (!compareAndSwapLong(o, offset, v, newValue)); return v; } public final Object getAndSetObject(Object o, long offset, Object newValue) { Object v; do { v = getObjectVolatile(o, offset); } while (!compareAndSwapObject(o, offset, v, newValue)); return v; }
基於 CAS 的一些原子操做實現,也是比較經常使用的方法。
//確保該欄杆前的讀操做不會和欄杆後的讀寫操做發生重排序 public native void loadFence(); //確保該欄杆前的寫操做不會和欄杆後的讀寫操做發生重排序 public native void storeFence(); //確保該欄杆前的讀寫操做不會和欄杆後的讀寫操做發生重排序 public native void fullFence(); //拋出非法訪問錯誤,僅用於VM內部 private static void throwIllegalAccessError() { throw new IllegalAccessError(); }
這是實現內存屏障的幾個方法,相似於 volatile 的語意,保證內存可見性和禁止重排序。這幾個方法涉及到 JMM(Java 內存模型),有興趣的可參考Java 內存模型 Cookbook 翻譯 。
相奕,互聯網開發者,多年互聯網開發經驗,關注底層技術與微服務周邊技術。
本文系做者投稿文章,歡迎投稿。投稿要求見:
https://my.oschina.net/editorial-story/blog/1814725
可參考已發佈文章: