JDK Unsafe 源碼徹底註釋

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

Unsafe 提供兩個功能:面試

  • 繞過 JVM 直接修改內存(對象)
  • 使用硬件 CPU 指令實現 CAS 原子操做

雖然咱們在通常的併發編程中不會直接用到 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;
    }
    ......
}

上面的代碼包含以下功能:安全

  • 本地靜態方法:registerNatives(),該方法會在靜態塊中執行
  • 私有構造函數:該類實例是單例的,不能實例化,能夠經過 getUnsafe() 方法獲取實例
  • 靜態單例方法:getUnsafe(),獲取實例
  • 靜態塊:包含初始化的註冊功能

要使用此類必須得到其實例,得到實例的方法是 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 爲內存絕對地址)。

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

  • 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 用於實驗。要獲取正確的結果,必須知足註釋裏的三個條件之一:

一、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 實例的偏移位置的值,這種狀況下代碼自己並不會報錯,但獲取到的值並不是該字段的值(未定義的值)

二、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);
  • 前兩個參數會被解釋成 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);

    /**
     * 阻塞當前線程,當發生以下狀況時返回:
     * 一、調用 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

可參考已發佈文章:

相關文章
相關標籤/搜索