JDK Unsafe 源碼完全註釋

併發作爲 Java 中非常重要的一部分,其內部大量使用了 Unsafe 類,它爲 java.util.concurrent 包中的類提供了底層支持。然而 Unsafe 並不是 JDK 的標準,它是 Sun 的內部實現,存在於 sun.misc 包中,在 Oracle 發行的 JDK 中並不包含其源代碼。

Unsafe 提供兩個功能:

繞過 JVM 直接修改內存(對象)
使用硬件 CPU 指令實現 CAS 原子操作
JDK Unsafe 源碼完全註釋

雖然我們在一般的併發編程中不會直接用到 Unsafe,但是很多 Java 基礎類庫與諸如 Netty、Cassandra 和 Kafka 等高性能庫都採用它,它在提升 Java 運行效率、增強 Java 語言底層操作能力方面起了很大作用。筆者覺得了解一個使用如此廣泛的庫還是很有必要的。本文將深入到 Unsafe 的源碼,分析一下它的邏輯。

本文使用 OpenJDK(jdk8-b120)中 Unsafe 的源碼,Unsafe 的實現是和虛擬機實現相關的,不同的虛擬機實現,它們的對象結構可能不一樣,這個 Unsafe 只能用於 Hotspot 虛擬機。

源碼查看: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;
    }
    ......
}

上面的代碼包含如下功能:

本地靜態方法:registerNatives(),該方法會在靜態塊中執行
私有構造函數:該類實例是單例的,不能實例化,可以通過 getUnsafe() 方法獲取實例
靜態單例方法:getUnsafe(),獲取實例
靜態塊:包含初始化的註冊功能
要使用此類必須獲得其實例,獲得實例的方法是 getUnsafe(),那就先看看這個方法。

getUnsafe() 方法包含一個註釋 @CallerSensitive,說明該方法不是誰都可以調用的。如果調用者不是由系統類加載器(bootstrap classloader)加載,則將拋出 SecurityException,所以默認情況下,應用代碼調用此方法將拋出異常。我們的代碼要想通過 getUnsafe() 獲取實例是不可能的了,不過可通過反射獲取 Unsafe 實例:

Field f= Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
U= (Unsafe) f.get(null);

此處通過反射獲取類的靜態字段,這樣就繞過了 getUnsafe() 的安全限制。

也可以通過反射獲取構造方法再實例化,但這樣違法了該類單例的原則,並且在使用上可能會有其它問題,所以不建議這樣做。

再來看看如何獲取指定變量的值:

public native int getInt(Object o, long offset);
獲取指定對象中指定偏移量的字段或數組元素的值,參數 o 是變量關聯的 Java 堆對象,如果指定的對象爲 null,則獲取內存中該地址的值(即 offset 爲內存絕對地址)。

如果不符合以下任意條件,則該方法返回的值是不確定的:

offset 通過 objectFieldOffset 方法獲取的類的某一字段的偏移量,並且關聯的對象 o 是該類的兼容對象(對象 o 所在的類必須是該類或該類的子類)
offset 通過 staticFieldOffset 方法獲取的類的某一字段的偏移量,o 是通過 staticFieldBase 方法獲取的對象
如果 o 引用的是數組,則 offset 的值爲 B+N*S,其中 N 是數組的合法下標,B 是通過 arrayBaseOffset 方法從該數組類獲取的,S 是通過 arrayIndexScale 方法從該數組類獲取的
如果以上任意條件符合,則調用者能獲取 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 用於實驗。要獲取正確的結果,必須滿足註釋裏的三個條件之一:

1、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

第一個參數 o 和 offset 都是從 MyObj 獲取的,所以返回 true。
第二個參數 o 是 MyObjChild 的實例,MyObjChild 是 MyObj 的子類,對象 o 是 MyObj 的兼容實例,所以返回 true。這從側面說明在虛擬機中子類的實例的內存結構繼承了父類的實例的內存結構。
第三個比較子類和父類中獲取的字段偏移量是否相同,返回 true 說明是一樣的,既然是一樣的,第四個自然就返回 true。
這裏重點說一下第五個,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 實例的偏移位置的值,這種情況下代碼本身並不會報錯,但獲取到的值並非該字段的值(未定義的值)

2、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。

3、如果 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);
前兩個參數會被解釋成 Java 變量(字段或數組)的引用,
參數給定的值會被存儲到該變量,變量的類型必須和方法參數的類型一致
參數 o 是變量關聯的 Java 堆對象,可以爲 null
參數 offset 代表該變量在該對象的位置,如果 o 是 null 則是內存的絕對地址
修改指定位置的內存,測試代碼:

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 的封裝,這樣該類就提供了動態獲取/釋放本地方法區內存的功能。

malloc 用於分配一個全新的未使用的連續內存,但該內存不會初始化,即不會被清零;
realloc 用於內存的縮容或擴容,有兩個參數,從 malloc 返回的地址和要調整的大小,該函數和 malloc 一樣,不會初始化,它能保留之前放到內存裏的值,很適合用於擴容;
free 用於釋放內存,該方法只有一個地址參數,那它如何知道要釋放多少個字節呢?其實在 malloc 分配內存的時候會多分配 4 個字節用於存放該塊的長度,比如 malloc(10) 其實會花費 14 個字節。理論上講能分配的最大內存是 4G(2^32-1)。在 hotspot 虛擬機的設計中,數組對象也有 4 個字節用於存放數組長度,那麼在 hotspot 中,數組的最大長度就是 2^32-1,這樣 free 函數只要讀取前 4 個字節就知道要釋放多少內存了(10+4);
memset  一般用於初始化內存,可以設置初始化內存的值,一般初始值會設置成 0,即清零操作。
來一個簡單的例子,申請內存-寫入內存-讀取內存-釋放內存:

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:

putXx 只是寫本線程緩存,不會將其它線程緩存置爲失效,所以不能保證其它線程一定看到此次修改;
putXxVolatile 相反,它可以保證其它線程一定看到此次修改;
putOrderedXx 也不保證其它線程一定看到此次修改,但和 putXx 又有區別,它的註釋上有兩個關鍵字:順序性(Ordered)和延遲性(lazy),順序性是指不會發生重排序,延遲性是指其它線程不會立即看到此次修改,只有當調用 putXxVolatile 使才能看到。


    /**
     * 釋放當前阻塞的線程。如果當前線程沒有阻塞,則下一次調用 park 不會阻塞。這個操作是"非安全"的
     * 是因爲調用者必須通過某種方式保證該線程沒有被銷燬
     *
     */
    public native void unpark(Object thread);

    /**
     * 阻塞當前線程,當發生如下情況時返回:
     * 1、調用 unpark 方法
     * 2、線程被中斷
     * 3、時間過期
     * 4、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 翻譯 。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章