【Java源碼計(jì)劃】Unsafe<rt.jar_sun.misc.Unsafe>

Unsafe

就像這個(gè)類名一樣,這個(gè)類本身就不是很安全,Unsafe類使Java擁有了像C語言的指針一樣操作內(nèi)存空間的能力,同時(shí)也帶來了指針的問題。過度的使用Unsafe類會(huì)使得出錯(cuò)的幾率變大,因此Java官方并不建議使用的,官方文檔也幾乎沒有。所有使用這類的都是依賴著一些stackoverflow上面或者查出來的一些不完善的資料來學(xué)習(xí)。因此我的翻譯和解讀也只能按照自己的理解來,希望大家一起探討。

Orcale的JDK8無法獲取到這個(gè)源碼,可以再OpenJDK中找到,或者通過IDEA等工具的反編譯功能看到

Unsafe并不在SE的范疇內(nèi),但是很多基礎(chǔ)類庫卻又都是基于Unsafe開發(fā)的,比如Netty、Cassandra、Hadoop、Kafka等,還有很多JUC都是用到了Unsafe,Java9 oracle要取消,由于我現(xiàn)在這個(gè)文檔是按照8來寫的,說實(shí)話9我也沒看,所以不清楚目前是什么情況,可能使用的時(shí)候需要按照J(rèn)igsaw一樣的去操作模塊,關(guān)于Java9中Unsafe的處理可以參考一下下面的內(nèi)容
1.What to do about sun.misc.Unsafe and Pals?文檔
2.stackoverflow上的一個(gè)問題
3.Oracle官方解決方案
4.為什么JUC中大量使用了sun.misc.Unsafe 這個(gè)類,但官方卻不建議開發(fā)者使用

說回來,感覺上這Unsafe就好像是Java的一個(gè)后門,這個(gè)類下面就是在訪問Native方法了,提供了一些繞開JVM的更底層功能,由此提高效率,使用的時(shí)候一定要注意,沒有深入了解輕易不要使用

  1. 就像上面說的Unsafe有可能在未來的Jdk版本移除或者不允許Java應(yīng)用代碼使用,這一點(diǎn)可能導(dǎo)致使用了Unsafe的應(yīng)用無法運(yùn)行在高版本的Jdk
  2. Unsafe的不少方法中必須提供原始地址(內(nèi)存地址)和被替換對象的地址,偏移量要自己計(jì)算,一旦出現(xiàn)問題就是JVM崩潰級別的異常,會(huì)導(dǎo)致整個(gè)JVM實(shí)例崩潰,表現(xiàn)為應(yīng)用程序直接crash掉
  3. Unsafe提供的直接內(nèi)存訪問的方法中使用的內(nèi)存不受JVM管理(無法被GC),需要手動(dòng)管理,一旦出現(xiàn)疏忽很有可能成為內(nèi)存泄漏的源頭。

Unsafe使用了單例模式,要用通過一個(gè)靜態(tài)方法getUnsafe()來獲取。但Unsafe類做了限制,如果是普通的調(diào)用的話,它會(huì)拋出一個(gè)SecurityException異常;只有由主類加載器加載的類才能調(diào)用這個(gè)方法,


@CallerSensitive
public static Unsafe getUnsafe() {
    //利用這個(gè)可以獲取調(diào)用者的類
    Class var0 = Reflection.getCallerClass();
    //這句話判斷類的加載器是不是主類加載器,也就是ClassLoader為null
    if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
        throw new SecurityException("Unsafe");
    } else {
        return theUnsafe;
    }
}

方法上面還有個(gè)注解,CallerSensitive,說起這個(gè)注解,其實(shí)是為了堵住漏洞用的。曾經(jīng)有黑客通過構(gòu)造雙重反射來提升權(quán)限,原理是當(dāng)時(shí)反射只檢查固定深度的調(diào)用者的類,看它有沒有特權(quán),例如固定看兩層的調(diào)用者(getCallerClass(2))。如果我的類本來沒足夠權(quán)限群訪問某些信息,那我就可以通過雙重反射去達(dá)到目的:反射相關(guān)的類是有很高權(quán)限的,而在 我->反射1->反射2 這樣的調(diào)用鏈上,反射2檢查權(quán)限時(shí)看到的是反射1的類,這就被欺騙了,導(dǎo)致安全漏洞。使用CallerSensitive后,getCallerClass不再用固定深度去尋找actual caller(“我”),而是把所有跟反射相關(guān)的接口方法都標(biāo)注上CallerSensitive,搜索時(shí)凡看到該注解都直接跳過,這樣就有效解決了前面舉例的問題

由于這個(gè)對于classloader的限制,想要在我們的一般代碼中使用,需在使用的時(shí)候還需要用點(diǎn)取巧的方式,比較常用的是利用反射來做

Field f = Unsafe.class.getDeclaredField("theUnsafe"); //Internal reference
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);

當(dāng)然也可以用像設(shè)置bootclasspath參數(shù)的方式來讓用戶代碼被主類加載器加載。

Unsafe還提供了很多內(nèi)容,像是

  1. 內(nèi)存管理,用于分配內(nèi)存、釋放內(nèi)存等
  2. 對非常規(guī)的對象實(shí)例化
  3. 操作類、對象、變量
  4. 數(shù)組操作
  5. 多線程同步
  6. 線程掛起與恢復(fù)
  7. 內(nèi)存屏障
  8. 等等

源碼解析

這個(gè)類中的大多數(shù)方法都是非常底層的,并且對應(yīng)于少量的硬件指令,鼓勵(lì)編譯器優(yōu)化相應(yīng)的方法

首先是一些常量,以及對應(yīng)的初始化代碼

//Unsafe實(shí)例(單例模式)
private static final Unsafe theUnsafe;
//非法的字段偏移量
public static final int INVALID_FIELD_OFFSET = -1;
//Boolean型數(shù)組偏移
public static final int ARRAY_BOOLEAN_BASE_OFFSET;
//Byte型數(shù)組偏移量
public static final int ARRAY_BYTE_BASE_OFFSET;
//short型數(shù)組偏移量
public static final int ARRAY_SHORT_BASE_OFFSET;
//char型數(shù)組偏移量
public static final int ARRAY_CHAR_BASE_OFFSET;
//int型數(shù)組偏移量
public static final int ARRAY_INT_BASE_OFFSET;
//long型數(shù)組偏移量
public static final int ARRAY_LONG_BASE_OFFSET;
//float型數(shù)組偏移量
public static final int ARRAY_FLOAT_BASE_OFFSET;
//double型數(shù)組偏移量
public static final int ARRAY_DOUBLE_BASE_OFFSET;
//Object型數(shù)組偏移量
public static final int ARRAY_OBJECT_BASE_OFFSET;

//boolean數(shù)組一個(gè)元素占用的字節(jié)數(shù)
public static final int ARRAY_BOOLEAN_INDEX_SCALE;
//byte數(shù)組一個(gè)元素占用的字節(jié)數(shù)
public static final int ARRAY_BYTE_INDEX_SCALE;
//short數(shù)組一個(gè)元素占用的字節(jié)數(shù)
public static final int ARRAY_SHORT_INDEX_SCALE;
//char數(shù)組一個(gè)元素占用的字節(jié)數(shù)
public static final int ARRAY_CHAR_INDEX_SCALE;
//int數(shù)組一個(gè)元素占用的字節(jié)數(shù)
public static final int ARRAY_INT_INDEX_SCALE;
//long數(shù)組一個(gè)元素占用的字節(jié)數(shù)
public static final int ARRAY_LONG_INDEX_SCALE;
//float數(shù)組一個(gè)元素占用的字節(jié)數(shù)
public static final int ARRAY_FLOAT_INDEX_SCALE;
//double數(shù)組一個(gè)元素占用的字節(jié)數(shù)
public static final int ARRAY_DOUBLE_INDEX_SCALE;
//Object數(shù)組一個(gè)元素占用的字節(jié)數(shù)
public static final int ARRAY_OBJECT_INDEX_SCALE;
//初始化了JVM的地址大小,如果是32位JVM這個(gè)值就是4,如果是64位的JVM,這個(gè)值就是8
public static final int ADDRESS_SIZE;


static {
        //調(diào)用注冊本地方法,以便JVM可以找到對應(yīng)的本地方法
        registerNatives();
        //注冊方法到反射過濾器,使得這個(gè)方法不能被反射得到,雖然并沒有什么用,
        //我們都是直接反射字段的
        Reflection.registerMethodsToFilter(Unsafe.class, new String[]{"getUnsafe"});
        //初始化Unsafe
        theUnsafe = new Unsafe();
        
        //初始化對應(yīng)的常量
        ARRAY_BOOLEAN_BASE_OFFSET = theUnsafe.arrayBaseOffset(boolean[].class);
        ARRAY_BYTE_BASE_OFFSET = theUnsafe.arrayBaseOffset(byte[].class);
        ARRAY_SHORT_BASE_OFFSET = theUnsafe.arrayBaseOffset(short[].class);
        ARRAY_CHAR_BASE_OFFSET = theUnsafe.arrayBaseOffset(char[].class);
        ARRAY_INT_BASE_OFFSET = theUnsafe.arrayBaseOffset(int[].class);
        ARRAY_LONG_BASE_OFFSET = theUnsafe.arrayBaseOffset(long[].class);
        ARRAY_FLOAT_BASE_OFFSET = theUnsafe.arrayBaseOffset(float[].class);
        ARRAY_DOUBLE_BASE_OFFSET = theUnsafe.arrayBaseOffset(double[].class);
        ARRAY_OBJECT_BASE_OFFSET = theUnsafe.arrayBaseOffset(Object[].class);
        ARRAY_BOOLEAN_INDEX_SCALE = theUnsafe.arrayIndexScale(boolean[].class);
        ARRAY_BYTE_INDEX_SCALE = theUnsafe.arrayIndexScale(byte[].class);
        ARRAY_SHORT_INDEX_SCALE = theUnsafe.arrayIndexScale(short[].class);
        ARRAY_CHAR_INDEX_SCALE = theUnsafe.arrayIndexScale(char[].class);
        ARRAY_INT_INDEX_SCALE = theUnsafe.arrayIndexScale(int[].class);
        ARRAY_LONG_INDEX_SCALE = theUnsafe.arrayIndexScale(long[].class);
        ARRAY_FLOAT_INDEX_SCALE = theUnsafe.arrayIndexScale(float[].class);
        ARRAY_DOUBLE_INDEX_SCALE = theUnsafe.arrayIndexScale(double[].class);
        ARRAY_OBJECT_INDEX_SCALE = theUnsafe.arrayIndexScale(Object[].class);
        ADDRESS_SIZE = theUnsafe.addressSize();
    }

接下來是一些讀寫操作,編譯器會(huì)把這些優(yōu)化為內(nèi)存操作,這些方法可以作用在位于Java堆中的對象字段,不能作用于封裝數(shù)組的元素

讀操作

通過給定的參數(shù)讀取值
更具體地說是通過給定的對象和給定的偏移來讀取一個(gè)字段或者是一個(gè)數(shù)組中元素的值,或者對象為null時(shí)讀出一個(gè)偏移量跟這個(gè)給定的偏移量相同的數(shù)值元素(詳見下文)

只有最少滿足以下情況中的一個(gè)時(shí)出現(xiàn)時(shí)結(jié)果才不為undefined

  • 偏移量是通過給定的一個(gè)Java字段的Field對象用下面的objectFieldOffset計(jì)算出來的,同時(shí)給定的對象也是和之前的Field獲取時(shí)的對象相匹配
  • 偏移量和給定的對象都是通過給定一個(gè)通過反射獲取的某個(gè)Java 字段的Field對象作為參數(shù),通過下面的staticFieldOffset方法和staticFieldBase方法獲取到的。
  • 對象所引用的是一個(gè)數(shù)組,偏移量是通過公式B+N * S 計(jì)算出來的一個(gè)整數(shù),其中N是數(shù)組中的一個(gè)合法的索引,B和S是通過調(diào)用下面的arrayBaseOffset和arrayIndexScale方法利用數(shù)組的類獲取出來的。返回的元素就是數(shù)組第N個(gè)值

值得注意的是,即便滿足了上面的某一個(gè)要求,調(diào)用也引用到了這個(gè)特定的Java變量,但是如果這個(gè)變量類型和方法的返回類型不匹配,此時(shí)也會(huì)是undefined

該方法通過兩個(gè)參數(shù)引用一個(gè)變量,這就意味著實(shí)際上它為Java對象提供了雙寄存器尋址方式。當(dāng)Object為null的時(shí)候,這個(gè)方法會(huì)將偏移量作為絕對地址。這種方式使用的時(shí)候類似于getInt(long)這個(gè)單參數(shù)的方法,單參數(shù)的方法為Java基本類型提供了單寄存器尋址方式。但是在內(nèi)存中這兩種變量有不同的布局,不能假設(shè)這兩種尋址模式是等效的。此外還應(yīng)該記住,雙寄存器尋址模式的偏移量不能與單寄存器尋址模式中使用的long混淆。

參數(shù)Object 是變量駐留的Java堆內(nèi)的對象,如果沒有可以為空
參數(shù)long 是變量在Java堆對象中駐留的位置(或者偏移量),如果為對象為null,就是變量抵制的靜態(tài)內(nèi)存地址。
返回對應(yīng)類型的從所給的指示地址中取出的值
可能會(huì)拋出RuntimeException

寫操作

將給定的值寫入給定的變量
前兩個(gè)參數(shù)作用跟getInt方法作用一致,用于引用特定的變量字段或者數(shù)組元素
最后一個(gè)變量則是要寫入的值。寫入值和目標(biāo)變量必須類型一致

其他方法類似,需要特殊說明的以注釋的方式寫在下面的方法上

    public native int getInt(Object var1, long var2);

    public native void putInt(Object var1, long var2, int var4);

    public native Object getObject(Object var1, long var2);
    
    //給出的第三個(gè)參數(shù),也就是要寫入的變量,要么是null
    //要么必須和目標(biāo)字段的類型匹配,否則結(jié)果可能是出錯(cuò)
    //如果第一個(gè)參數(shù)也就是對象不為null,
    //那么內(nèi)存標(biāo)志或者其他的內(nèi)存屏障在虛擬機(jī)需要的時(shí)候會(huì)被更新
    //關(guān)于內(nèi)存這部分詳見其他的有關(guān)內(nèi)存的文章
    public native void putObject(Object var1, long var2, Object var4);

    public native boolean getBoolean(Object var1, long var2);

    public native void putBoolean(Object var1, long var2, boolean var4);

    public native byte getByte(Object var1, long var2);

    public native void putByte(Object var1, long var2, byte var4);

    public native short getShort(Object var1, long var2);

    public native void putShort(Object var1, long var2, short var4);

    public native char getChar(Object var1, long var2);

    public native void putChar(Object var1, long var2, char var4);

    public native long getLong(Object var1, long var2);

    public native void putLong(Object var1, long var2, long var4);

    public native float getFloat(Object var1, long var2);

    public native void putFloat(Object var1, long var2, float var4);

    public native double getDouble(Object var1, long var2);

    public native void putDouble(Object var1, long var2, double var4);

下面這些方法在以前也是Native的方法,使用32bit的偏移量,現(xiàn)在全部改成了對上面方法的封裝,將偏移量強(qiáng)制轉(zhuǎn)換為long型。能夠提供對1.4編譯出來的字節(jié)碼的向后兼容性


 /** @deprecated */
    @Deprecated
    public int getInt(Object var1, int var2) {
        return this.getInt(var1, (long)var2);
    }

    /** @deprecated */
    @Deprecated
    public void putInt(Object var1, int var2, int var3) {
        this.putInt(var1, (long)var2, var3);
    }

    /** @deprecated */
    @Deprecated
    public Object getObject(Object var1, int var2) {
        return this.getObject(var1, (long)var2);
    }

    /** @deprecated */
    @Deprecated
    public void putObject(Object var1, int var2, Object var3) {
        this.putObject(var1, (long)var2, var3);
    }

    /** @deprecated */
    @Deprecated
    public boolean getBoolean(Object var1, int var2) {
        return this.getBoolean(var1, (long)var2);
    }

    /** @deprecated */
    @Deprecated
    public void putBoolean(Object var1, int var2, boolean var3) {
        this.putBoolean(var1, (long)var2, var3);
    }

    /** @deprecated */
    @Deprecated
    public byte getByte(Object var1, int var2) {
        return this.getByte(var1, (long)var2);
    }

    /** @deprecated */
    @Deprecated
    public void putByte(Object var1, int var2, byte var3) {
        this.putByte(var1, (long)var2, var3);
    }

    /** @deprecated */
    @Deprecated
    public short getShort(Object var1, int var2) {
        return this.getShort(var1, (long)var2);
    }

    /** @deprecated */
    @Deprecated
    public void putShort(Object var1, int var2, short var3) {
        this.putShort(var1, (long)var2, var3);
    }

    /** @deprecated */
    @Deprecated
    public char getChar(Object var1, int var2) {
        return this.getChar(var1, (long)var2);
    }

    /** @deprecated */
    @Deprecated
    public void putChar(Object var1, int var2, char var3) {
        this.putChar(var1, (long)var2, var3);
    }

    /** @deprecated */
    @Deprecated
    public long getLong(Object var1, int var2) {
        return this.getLong(var1, (long)var2);
    }

    /** @deprecated */
    @Deprecated
    public void putLong(Object var1, int var2, long var3) {
        this.putLong(var1, (long)var2, var3);
    }

    /** @deprecated */
    @Deprecated
    public float getFloat(Object var1, int var2) {
        return this.getFloat(var1, (long)var2);
    }

    /** @deprecated */
    @Deprecated
    public void putFloat(Object var1, int var2, float var3) {
        this.putFloat(var1, (long)var2, var3);
    }

    /** @deprecated */
    @Deprecated
    public double getDouble(Object var1, int var2) {
        return this.getDouble(var1, (long)var2);
    }

    /** @deprecated */
    @Deprecated
    public void putDouble(Object var1, int var2, double var3) {
        this.putDouble(var1, (long)var2, var3);
    }

下面這些方法工作于處于c堆的變量

讀操作

類似的,用于從給定的內(nèi)存地址中獲取值,如果給定的地址是0或者指向的地址不是一個(gè)通過下面的allocateMemory獲取到的塊,那么結(jié)果可能是未定義的。

寫操作

把給定的元素寫入給定的內(nèi)存地址。如果給定的內(nèi)存地址是0或者沒有指向一個(gè)通過下面的allocateMemory獲取到的塊,結(jié)果可能是未定義的。

    public native byte getByte(long var1);

    public native void putByte(long var1, byte var3);

    public native short getShort(long var1);

    public native void putShort(long var1, short var3);

    public native char getChar(long var1);

    public native void putChar(long var1, char var3);

    public native int getInt(long var1);

    public native void putInt(long var1, int var3);

    public native long getLong(long var1);

    public native void putLong(long var1, long var3);

    public native float getFloat(long var1);

    public native void putFloat(long var1, float var3);

    public native double getDouble(long var1);

    public native void putDouble(long var1, double var3);

   

通過給定的內(nèi)存地址,獲得本地指針,如果給定的地址是0或者沒有指向一個(gè)通過下面的allocateMemory獲取到的塊,結(jié)果是未定義的

如果本地指針的的寬度不夠64位,那么這個(gè)地址會(huì)作為一個(gè)無符號數(shù)擴(kuò)展到Java的long型。指針可以通過任何給定字節(jié)數(shù)的偏移量來索引,只需簡單地把這個(gè)偏移量加到j(luò)ava的long型上。真正讀取目標(biāo)地址使用的字節(jié)數(shù)可以通過調(diào)用下面addresssize方法獲取(實(shí)際上也就是vm的位數(shù),如4個(gè)字節(jié)或者8個(gè)字節(jié)對應(yīng)32位和64位)

    public native long getAddress(long var1);

把本地指針存到給定的內(nèi)存地址中,如果地址是0,或者指向的塊不是一個(gè)通過allocateMemory獲取的塊,那么結(jié)果可能是未定義的。

同上一樣,真正寫入的目標(biāo)地址的字節(jié)數(shù)可以通過addressSize獲取。


    public native void putAddress(long var1, long var3);

下面的方法是封裝了c語言對應(yīng)的malloc, realloc, free方法以及做出的一些增補(bǔ)

分配一個(gè)新的本地內(nèi)存塊,大小是給定的數(shù)量的字節(jié)數(shù)。內(nèi)存中的內(nèi)容是未初始化的,一般來說是無意義的垃圾數(shù)據(jù)。返回的本地指針不能是0,并且和所有的元素類型對齊。通過調(diào)用freeMemory方法釋放這些內(nèi)存,或者通過reallocateMemory來重新調(diào)整大小。

如果大小是非法的或者對于本地類型的大小太大,會(huì)拋出IllegalArgumentException

當(dāng)系統(tǒng)拒絕分配內(nèi)存的時(shí)候會(huì)拋出OutOfMemoryError

public native long allocateMemory(long bytes);

重新在內(nèi)存中增加給定數(shù)量字節(jié)數(shù)的新內(nèi)容,這些位于之前塊后面的新塊都是未經(jīng)初始化的,通常數(shù)據(jù)內(nèi)容都是沒有意義的垃圾數(shù)據(jù)。當(dāng)且僅當(dāng)請求的塊大小為0的時(shí)候返回0。這個(gè)結(jié)果的本地指針也是一樣和所有元素類型對齊。通過freeMemory方法釋放這些內(nèi)存,或者通過reallocateMemory來重新調(diào)整大小。這個(gè)方法中如果給出的address為null,這個(gè)時(shí)候方法會(huì)執(zhí)行分配方法,相當(dāng)于退化為上一個(gè)方法了

異常情況同上

public native long reallocateMemory(long address, long bytes);

從1.7開始新增了一些方法如下以及一些封裝了這些方法的調(diào)用接口。

Native方法將給定內(nèi)存塊中的所有字節(jié)設(shè)置為固定值(通常是0)

這個(gè)方法通過兩個(gè)參數(shù)確定一個(gè)塊的base地址,所以跟前面討論的一樣,因此這個(gè)方法提供了一個(gè)雙寄存器尋址方式。當(dāng)Object的引用為null的時(shí)候,偏移量offset就提供了一個(gè)絕對的base地址。

存儲是連續(xù)(原子)的單元,由地址和長度兩個(gè)參數(shù)確定。如果有效地址和長度都是8的倍數(shù),這個(gè)時(shí)候都是用long存儲,如果有效地址和長度分別能被4或者2整除,則以int或者short來存儲。

封裝的方法等效于setMemory(null, address, bytes, value),也就是Object為null的情況。

 public native void setMemory(Object o, long offset, long bytes, byte value);
 
 public void setMemory(long address, long bytes, byte value) {
         setMemory(null, address, bytes, value);
  }

把目標(biāo)塊的存儲內(nèi)容拷貝到另一個(gè)塊中。跟上面描述的一樣,目標(biāo)塊和源塊都是用兩個(gè)參數(shù)確定的,所以也都和上面一樣提供雙寄存器的尋址方式。當(dāng)Object的引用為null的時(shí)候,offset提供一個(gè)絕對的base地址。

跟上面一樣,這個(gè)傳輸都是在連續(xù)(原子)的單元中進(jìn)行,這寫單元通過地址和長度共同決定。如果有效地址和長度都是8的倍數(shù),這個(gè)時(shí)候都是用long存儲,如果有效地址和長度分別能被4或者2整除,則以int或者short來存儲。

這個(gè)方法也是1.7增加的

封裝方法等效于copyMemory(null, srcAddress, null, destAddress, bytes)也就是Object為null的情況

public native void copyMemory(Object srcBase, long srcOffset,
                                 Object destBase, long destOffset,
                                  long bytes);
                                  
public void copyMemory(long srcAddress, long destAddress, long bytes) {
          copyMemory(null, srcAddress, null, destAddress, bytes);
 }                                

釋放一個(gè)通過allocateMemory或者reallocateMemory獲取的本地內(nèi)存塊,如果傳遞的地址為null,此時(shí)不做任何操作。

public native void freeMemory(long address);

下面是一些隨機(jī)查找方法

返回指定字段的偏移量,轉(zhuǎn)換為32位的,也就是int型,這個(gè)方法實(shí)現(xiàn)如下
注意:這個(gè)方法是封裝了long返回的方法

@Deprecated
  public int fieldOffset(Field f) {
        //Field.getModifiers()方法可以獲取修飾符(一個(gè)十六進(jìn)制)
        //判斷是不是靜態(tài)修飾可以用Modifier類中的相關(guān)靜態(tài)方法來判斷
        if (Modifier.isStatic(f.getModifiers()))
            //調(diào)用staticFieldOffset獲取給定的靜態(tài)Field域的偏移量
            return (int) staticFieldOffset(f);
        else
            //調(diào)用objectFieldOffset獲取給定Field域的偏移量
            return (int) objectFieldOffset(f);
  }

返回訪問指定類的靜態(tài)字段的base地址,實(shí)現(xiàn)如下
注意:從1.4開始使用staticFieldBase來實(shí)現(xiàn)讀取特定的Field來讀取靜態(tài)域的base。這個(gè)方法只能工作在把所有靜態(tài)域存在一起的JVM。

@Deprecated
public Object staticFieldBase(Class c) {
       //讀取所有Class中的field
       Field[] fields = c.getDeclaredFields();
       //循環(huán)讀取Field
       for (int i = 0; i < fields.length; i++) {
            //找到第一個(gè)static字段后,用這個(gè)字段計(jì)算base地址
           if (Modifier.isStatic(fields[i].getModifiers())) {
               return staticFieldBase(fields[i]);
           }
       }
       //沒有靜態(tài)字段
       return null;
}

報(bào)告給定的靜態(tài)Field在它所屬的類申請的空間中的位置。不要在這個(gè)偏移量上進(jìn)行任何的算數(shù)。
因?yàn)檫@個(gè)結(jié)果只是一個(gè)傳遞給unsafe 堆的訪問器用的cookie。

任何給定的字段都始終具有相同的偏移量和base,同一個(gè)類的任何兩個(gè)不同字段都沒有相同的偏移量和base

注:從1.4開始,F(xiàn)ield的偏移量用long標(biāo)示,盡管sun的JVM沒有使用32位的形式來表示這個(gè)地址,但是這樣對于將靜態(tài)字段存儲在絕對地址的jvm實(shí)現(xiàn),可以直接利用類似getInt(object,long)的調(diào)用形式,通過null和long來定位字段,因此,對于需要將代碼移植到64位平臺的情況來說,保留這種完整的偏移量無疑更加好用。

public native long staticFieldOffset(Field f);

這個(gè)方法報(bào)告了一個(gè)字段相對對象的偏移地址,跟上面說的一樣,也不要嘗試在這個(gè)偏移量上進(jìn)行算術(shù)運(yùn)算。

注意:出了跟上面一樣值得注意的地方以外,這個(gè)地方返回的相對偏移量是個(gè)long主要是為了保證前后連貫,畢竟如果沒有這一層考慮,很難想象出哪個(gè)對象的偏移量居然需要那么多bits才能表示出來。

public native long objectFieldOffset(Field f);

通過給定字段獲取Object對象(如果存在的話),結(jié)合這個(gè)方法通過getInt等方法可以獲取靜態(tài)字段

注意,這個(gè)方法的返回對象可能是null,也可能是一個(gè)對象,但是這個(gè)對象也不是完整可信的,這也只是一個(gè)cookie,不要把它當(dāng)做一個(gè)真實(shí)的對象,所以不要以任何方式使用它,除非是在本類中的 get和put類的方法中。

public native Object staticFieldBase(Field f);

這個(gè)方法用于確保給點(diǎn)大哥class已經(jīng)被初始化了,這個(gè)方法通常用于在獲取一個(gè)類靜態(tài)字段的base之前

public native void ensureClassInitialized(Class c);

報(bào)告給定數(shù)組類的第一個(gè)元素在存儲中所在的位置。
如果arrayIndexScale方法返回的值不為零,那么可以通過這個(gè)base和scale一起計(jì)算出新的偏移量,然后訪問給定數(shù)組的指定元素。

public native int arrayBaseOffset(Class arrayClass);

讀取給定數(shù)組類在存儲中的每個(gè)元素的偏移量,但是窄類型的數(shù)組通常不能跟類似getByte這種方法一起工作,所以這種類返回的這單個(gè)元素的偏移量就是0.(窄類型我的理解是類似接口創(chuàng)建的數(shù)組,沒有初始化的這種)

public native int arrayIndexScale(Class arrayClass);

返回本地指針的字節(jié)數(shù),這個(gè)值可能是4或者8,4代表32位,8代表64位。注意其他基本類型的地址大小完全由他們的存儲的內(nèi)容決定

public native int addressSize();

這個(gè)方法以字節(jié)為單位返回本機(jī)內(nèi)存頁的大小,這個(gè)數(shù)字通常是2的冪


 public native int pageSize();

下面是一些JNI的隨機(jī)可信操作

這個(gè)方法告知虛擬機(jī)定義一個(gè)類,并且不進(jìn)行安全檢查。默認(rèn)情況下類加載器和保護(hù)域來自調(diào)用類

public native Class defineClass(String name, byte[] b, int off, int len,
                                     ClassLoader loader,
                                     ProtectionDomain protectionDomain);
                                     
 public native Class defineClass(String name, byte[] b, int off, int len);

這個(gè)方法定義一個(gè)類,但是不讓類加載器和系統(tǒng)字典知道這個(gè)類。注意這個(gè)類跟真正匿名類有點(diǎn)不一樣。這個(gè)方法在java.lang.invoke.InnerClassLambdaMetafactory中有應(yīng)用,這個(gè)類的源碼找的時(shí)候可以通過LambdaMetafactory這個(gè)類點(diǎn)過來

這里有個(gè)詞叫CP應(yīng)該指的是Constant Pool

對于每一個(gè)CP的項(xiàng),對應(yīng)的CP patch要么是null要么就必須和標(biāo)簽匹配

對于Integer, Long, Float, Double:就是java.lang下面的對應(yīng)封裝類型
Utf8:就是字符串(如果用于名字或者簽名,語法上必須正確)
Class:任何java.lang.Class的對象
String:任何對象(不單純是java.lang.String)
InterfaceMethodRef:在調(diào)用點(diǎn)的參數(shù)上執(zhí)行方法的句柄

參數(shù)中
hostClass 是宿主類,也就是鏈接、訪問控制、保護(hù)域和類加載器的上下文
data 是類文件的字節(jié)碼
cpPathces 就是非空的cp項(xiàng)了,用來替換data中對應(yīng)的cp

這個(gè)方法可以應(yīng)用于需要?jiǎng)討B(tài)生成很多結(jié)構(gòu)相同,只不過有一些變量是不同的類這種場景
這個(gè)方法構(gòu)造出來的類智能通過返回的Class反射來進(jìn)行操作

關(guān)于這個(gè)方法給一個(gè)參考鏈接,知乎上一個(gè)回答

public native Class defineAnonymousClass(Class hostClass, byte[] data, Object[] cpPatches);

通過給定的Class對象創(chuàng)建一個(gè)類的示例,不執(zhí)行構(gòu)造過程,如果類沒有初始化,執(zhí)行初始化。

public native Object allocateInstance(Class cls)   throws InstantiationException;

鎖定給定的對象,解鎖必須通過monitorExit方法

public native void monitorEnter(Object o);

解鎖給定的對象,前提是必須是加鎖了的對象

public native void monitorExit(Object o);

嘗試對對象進(jìn)行加鎖。返回的true和false標(biāo)示了加鎖是否成功,如果成功了這個(gè)對象就算是加上鎖了。

 public native boolean tryMonitorEnter(Object o);

在不通過校驗(yàn)器的情況下拋出一個(gè)異常


public native void throwException(Throwable ee);

CAS操作
原子操作更新變量x,如果當(dāng)前對象o是(持有)expected,將對象更新為x

其中o為目標(biāo)對象,offset為偏移地址,expected為期待的值,x為目標(biāo)值

更新成功返回true,失敗返回false

其他幾個(gè)類似

public final native boolean compareAndSwapObject(Object o, long offset,
                                                      Object expected,
                                                      Object x);
                                                      
public final native boolean compareAndSwapInt(Object o, long offset,
                                                     int expected,
                                                     int x);
             
 public final native boolean compareAndSwapLong(Object o, long offset,
                                                    long expected,
                                                    long x);

下面的都是上面提到過的getXXX的方法的Volatile版本。get方法獲取給定的java變量的值,附加了volatile加載的語義
put方法存儲一個(gè)值到給定的java變量同樣附加了volatile存儲語義

public native Object getObjectVolatile(Object var1, long var2);

    public native void putObjectVolatile(Object var1, long var2, Object var4);

    public native int getIntVolatile(Object var1, long var2);

    public native void putIntVolatile(Object var1, long var2, int var4);

    public native boolean getBooleanVolatile(Object var1, long var2);

    public native void putBooleanVolatile(Object var1, long var2, boolean var4);

    public native byte getByteVolatile(Object var1, long var2);

    public native void putByteVolatile(Object var1, long var2, byte var4);

    public native short getShortVolatile(Object var1, long var2);

    public native void putShortVolatile(Object var1, long var2, short var4);

    public native char getCharVolatile(Object var1, long var2);

    public native void putCharVolatile(Object var1, long var2, char var4);

    public native long getLongVolatile(Object var1, long var2);

    public native void putLongVolatile(Object var1, long var2, long var4);

    public native float getFloatVolatile(Object var1, long var2);

    public native void putFloatVolatile(Object var1, long var2, float var4);

    public native double getDoubleVolatile(Object var1, long var2);

    public native void putDoubleVolatile(Object var1, long var2, double var4);

接下來是putObjectVolatile這個(gè)方法的一個(gè)版本,不能保證存儲對其他線程的立即可見,也就是說這個(gè)更改是有延遲的。這個(gè)方法通常只有在底層字段是volatile修飾的時(shí)候才能生效(除非是數(shù)組元素)
其他兩個(gè)類似

 public native void    putOrderedObject(Object o, long offset, Object x);
 
 public native void    putOrderedInt(Object o, long offset, int x);
 
 public native void    putOrderedLong(Object o, long offset, long x);

下面是關(guān)于線程的方法

解除被park阻塞的線程的阻塞,或者如果這個(gè)線程并沒有被阻塞,會(huì)導(dǎo)致后續(xù)的對于park的調(diào)用無法成功阻塞

注意:這個(gè)操作并不安全,不安全主要是由于調(diào)用方必須以某種方式確保線程沒有被銷毀。單純在Java中判斷線程存活通常不需要做什么特別的操作來確保(線程的引用通常都是存活的),但是在原生中這幾乎是不可能自動(dòng)完成的

 public native void unpark(Object thread);

阻塞當(dāng)前線程,直到unpark被調(diào)用,或者在這個(gè)park方法之前調(diào)用過unpark方法,這就意味著unpark已經(jīng)出現(xiàn)過了,或者線程中斷或者time的時(shí)間到了。

在time不為0的情況下,如果isAbsolute為true,time就是相當(dāng)于時(shí)間戳的毫秒數(shù),如果isAbsolute為falsetime就表示納秒,或者特殊情況可能會(huì)出現(xiàn)無任何理由的返回。

關(guān)于這個(gè)方法的使用可以參考源碼中java.util.concurrent.locks.LockSupport類,關(guān)于park的支持基本上底層都是unsafe的park方法

public native void park(boolean isAbsolute, long time);

這個(gè)方法可以獲取系統(tǒng)的平均負(fù)載值,具體來說就是系統(tǒng)運(yùn)行隊(duì)列分配給可用處理器的平均負(fù)載。

這個(gè)方法檢索nelems數(shù)量的樣本,loadavg這個(gè)double型的數(shù)組存放對應(yīng)負(fù)載值的結(jié)果,注意系統(tǒng)最多可以取三種樣本值,也就是nelems只能取三種1,2,3分別對應(yīng)1,5,15分鐘內(nèi)系統(tǒng)的樣本。

如果無法獲取系統(tǒng)的負(fù)載,這個(gè)方法會(huì)返回-1,否則則返回獲取到的樣本數(shù)量,也就是loadavg中有效的元素個(gè)數(shù)

public native int getLoadAverage(double[] loadavg, int nelems);

下面是JDK1.8增加的內(nèi)存屏障相關(guān)內(nèi)容,

//這個(gè)方法前面的所有讀操作一定在load屏障之前執(zhí)行完成
public native void loadFence();
//這個(gè)方法之前的所有寫操作,一定在store屏障之前執(zhí)行完成
public native void storeFence();
//這個(gè)方法之前的讀寫操作,都在full屏障之前執(zhí)行完成
public native void fullFence();

下面是1.8加的getAndAdd系列方法

基于CAS和volatile系列方法組合出來的給某個(gè)元素添加值(或者設(shè)置值)得方法

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;
    }
    
    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;
    }

還有最后給虛擬機(jī)用的拋出非法訪問異常的方法


private static void throwIllegalAccessError() {
       throw new IllegalAccessError();
    }

下面是一些參考的內(nèi)容:
JAVA中神奇的雙刃劍--Unsafe
在openjdk8下看Unsafe源碼
Java魔法類:sun.misc.Unsafe

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,658評論 1 32
  • 原文鏈接:Java并發(fā)編程-無鎖CAS與Unsafe類及其并發(fā)包Atomic - CSDN博客 在前面一篇博文中,...
    Walter_Hu閱讀 1,043評論 0 3
  • 第二部分 自動(dòng)內(nèi)存管理機(jī)制 第二章 java內(nèi)存異常與內(nèi)存溢出異常 運(yùn)行數(shù)據(jù)區(qū)域 程序計(jì)數(shù)器:當(dāng)前線程所執(zhí)行的字節(jié)...
    小明oh閱讀 1,282評論 0 2
  • Java8張圖 11、字符串不變性 12、equals()方法、hashCode()方法的區(qū)別 13、...
    Miley_MOJIE閱讀 3,896評論 0 11
  • 第一個(gè):壓縮圖片 當(dāng)我們上傳圖片到服務(wù)器時(shí),需要壓縮一下圖片的質(zhì)量。方法如下: 第二個(gè),截取人臉部圖片 拍攝身份證...
    ZYiDa閱讀 830評論 0 0

友情鏈接更多精彩內(nèi)容