新聞中心
大家好,我是程序員鼓勵(lì)師美美~
成都創(chuàng)新互聯(lián)是一家集網(wǎng)站建設(shè),澤庫(kù)企業(yè)網(wǎng)站建設(shè),澤庫(kù)品牌網(wǎng)站建設(shè),網(wǎng)站定制,澤庫(kù)網(wǎng)站建設(shè)報(bào)價(jià),網(wǎng)絡(luò)營(yíng)銷(xiāo),網(wǎng)絡(luò)優(yōu)化,澤庫(kù)網(wǎng)站推廣為一體的創(chuàng)新建站企業(yè),幫助傳統(tǒng)企業(yè)提升企業(yè)形象加強(qiáng)企業(yè)競(jìng)爭(zhēng)力??沙浞譂M足這一群體相比中小企業(yè)更為豐富、高端、多元的互聯(lián)網(wǎng)需求。同時(shí)我們時(shí)刻保持專(zhuān)業(yè)、時(shí)尚、前沿,時(shí)刻以成就客戶成長(zhǎng)自我,堅(jiān)持不斷學(xué)習(xí)、思考、沉淀、凈化自己,讓我們?yōu)楦嗟钠髽I(yè)打造出實(shí)用型網(wǎng)站。
《基本功》專(zhuān)欄又上新了:Java中的Unsafe類(lèi)在提升運(yùn)行效率、增強(qiáng)底層資源操作能力方面有很大的用處。但如果在開(kāi)發(fā)過(guò)程中使用不當(dāng),就會(huì)出現(xiàn)各種“莫名其妙”的問(wèn)題。
本篇文章將會(huì)帶領(lǐng)你重新認(rèn)識(shí)它,繞過(guò)“開(kāi)發(fā)雷區(qū)”,豬事大吉。
Unsafe是位于sun.misc包下的一個(gè)類(lèi),主要提供一些用于執(zhí)行低級(jí)別、不安全操作的方法,如直接訪問(wèn)系統(tǒng)內(nèi)存資源、自主管理內(nèi)存資源等,這些方法在提升Java運(yùn)行效率、增強(qiáng)Java語(yǔ)言底層資源操作能力方面起到了很大的作用。但由于Unsafe類(lèi)使Java語(yǔ)言擁有了類(lèi)似C語(yǔ)言指針一樣操作內(nèi)存空間的能力,這無(wú)疑也增加了程序發(fā)生相關(guān)指針問(wèn)題的風(fēng)險(xiǎn)。在程序中過(guò)度、不正確使用Unsafe類(lèi)會(huì)使得程序出錯(cuò)的概率變大,使得Java這種安全的語(yǔ)言變得不再“安全”,因此對(duì)Unsafe的使用一定要慎重。
本文對(duì)sun.misc.Unsafe公共API功能及相關(guān)應(yīng)用場(chǎng)景進(jìn)行介紹。
基本介紹
如下Unsafe源碼所示,Unsafe類(lèi)為一單例實(shí)現(xiàn),提供靜態(tài)方法getUnsafe獲取Unsafe實(shí)例,當(dāng)且僅當(dāng)調(diào)用getUnsafe方法的類(lèi)為引導(dǎo)類(lèi)加載器所加載時(shí)才合法,否則拋出SecurityException異常。
public final class Unsafe {
// 單例對(duì)象
private static final Unsafe theUnsafe;
private Unsafe() {
}
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
// 僅在引導(dǎo)類(lèi)加載器`BootstrapClassLoader`加載時(shí)才合法
if(!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
}
那如若想使用這個(gè)類(lèi),該如何獲取其實(shí)例?有如下兩個(gè)可行方案。
其一,從getUnsafe
方法的使用限制條件出發(fā),通過(guò)Java命令行命令-Xbootclasspath/a
把調(diào)用Unsafe相關(guān)方法的類(lèi)A所在jar包路徑追加到默認(rèn)的bootstrap路徑中,使得A被引導(dǎo)類(lèi)加載器加載,從而通過(guò)Unsafe.getUnsafe
方法安全的獲取Unsafe實(shí)例。
java -Xbootclasspath/a: ${path} // 其中path為調(diào)用Unsafe相關(guān)方法的類(lèi)所在jar包路徑
其二,通過(guò)反射獲取單例對(duì)象theUnsafe。
private static Unsafe reflectGetUnsafe() {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe) field.get(null);
} catch (Exception e) {
log.error(e.getMessage(), e);
return null;
}
}
功能介紹

如上圖所示,Unsafe提供的API大致可分為內(nèi)存操作、CAS、Class相關(guān)、對(duì)象操作、線程調(diào)度、系統(tǒng)信息獲取、內(nèi)存屏障、數(shù)組操作等幾類(lèi),下面將對(duì)其相關(guān)方法和應(yīng)用場(chǎng)景進(jìn)行詳細(xì)介紹。
內(nèi)存操作
這部分主要包含堆外內(nèi)存的分配、拷貝、釋放、給定地址值操作等方法。
//分配內(nèi)存, 相當(dāng)于C++的malloc函數(shù)
public native long allocateMemory(long bytes);
//擴(kuò)充內(nèi)存
public native long reallocateMemory(long address, long bytes);
//釋放內(nèi)存
public native void freeMemory(long address);
//在給定的內(nèi)存塊中設(shè)置值
public native void setMemory(Object o, long offset, long bytes, byte value);
//內(nèi)存拷貝
public native void copyMemory(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes);
//獲取給定地址值,忽略修飾限定符的限制訪問(wèn)限制。與此類(lèi)似操作還有: getInt,getDouble,getLong,getChar等
public native Object getObject(Object o, long offset);
//為給定地址設(shè)置值,忽略修飾限定符的訪問(wèn)限制,與此類(lèi)似操作還有: putInt,putDouble,putLong,putChar等
public native void putObject(Object o, long offset, Object x);
//獲取給定地址的byte類(lèi)型的值(當(dāng)且僅當(dāng)該內(nèi)存地址為allocateMemory分配時(shí),此方法結(jié)果為確定的)
public native byte getByte(long address);
//為給定地址設(shè)置byte類(lèi)型的值(當(dāng)且僅當(dāng)該內(nèi)存地址為allocateMemory分配時(shí),此方法結(jié)果才是確定的)
public native void putByte(long address, byte x);
通常,我們?cè)贘ava中創(chuàng)建的對(duì)象都處于堆內(nèi)內(nèi)存(heap)中,堆內(nèi)內(nèi)存是由JVM所管控的Java進(jìn)程內(nèi)存,并且它們遵循JVM的內(nèi)存管理機(jī)制,JVM會(huì)采用垃圾回收機(jī)制統(tǒng)一管理堆內(nèi)存。與之相對(duì)的是堆外內(nèi)存,存在于JVM管控之外的內(nèi)存區(qū)域,Java中對(duì)堆外內(nèi)存的操作,依賴于Unsafe提供的操作堆外內(nèi)存的native方法。
使用堆外內(nèi)存的原因
對(duì)垃圾回收停頓的改善。由于堆外內(nèi)存是直接受操作系統(tǒng)管理而不是JVM,所以當(dāng)我們使用堆外內(nèi)存時(shí),即可保持較小的堆內(nèi)內(nèi)存規(guī)模,從而在GC時(shí)減少回收停頓對(duì)于應(yīng)用的影響。
提升程序I/O操作的性能。通常在I/O通信過(guò)程中,會(huì)存在堆內(nèi)內(nèi)存到堆外內(nèi)存的數(shù)據(jù)拷貝操作,對(duì)于需要頻繁進(jìn)行內(nèi)存間數(shù)據(jù)拷貝且生命周期較短的暫存數(shù)據(jù),都建議存儲(chǔ)到堆外內(nèi)存。
典型應(yīng)用
DirectByteBuffer是Java用于實(shí)現(xiàn)堆外內(nèi)存的一個(gè)重要類(lèi),通常用在通信過(guò)程中做緩沖池,如在Netty、MINA等NIO框架中應(yīng)用廣泛。DirectByteBuffer對(duì)于堆外內(nèi)存的創(chuàng)建、使用、銷(xiāo)毀等邏輯均由Unsafe提供的堆外內(nèi)存API來(lái)實(shí)現(xiàn)。
下圖為DirectByteBuffer構(gòu)造函數(shù),創(chuàng)建DirectByteBuffer的時(shí)候,通過(guò)Unsafe.allocateMemory分配內(nèi)存、Unsafe.setMemory進(jìn)行內(nèi)存初始化,而后構(gòu)建Cleaner對(duì)象用于跟蹤DirectByteBuffer對(duì)象的垃圾回收,以實(shí)現(xiàn)當(dāng)DirectByteBuffer被垃圾回收時(shí),分配的堆外內(nèi)存一起被釋放。

那么如何通過(guò)構(gòu)建垃圾回收追蹤對(duì)象Cleaner實(shí)現(xiàn)堆外內(nèi)存釋放呢?
Cleaner繼承自Java四大引用類(lèi)型之一的虛引用PhantomReference(眾所周知,無(wú)法通過(guò)虛引用獲取與之關(guān)聯(lián)的對(duì)象實(shí)例,且當(dāng)對(duì)象僅被虛引用引用時(shí),在任何發(fā)生GC的時(shí)候,其均可被回收),通常PhantomReference與引用隊(duì)列ReferenceQueue結(jié)合使用,可以實(shí)現(xiàn)虛引用關(guān)聯(lián)對(duì)象被垃圾回收時(shí)能夠進(jìn)行系統(tǒng)通知、資源清理等功能。如下圖所示,當(dāng)某個(gè)被Cleaner引用的對(duì)象將被回收時(shí),JVM垃圾收集器會(huì)將此對(duì)象的引用放入到對(duì)象引用中的pending鏈表中,等待Reference-Handler進(jìn)行相關(guān)處理。其中,Reference-Handler為一個(gè)擁有最高優(yōu)先級(jí)的守護(hù)線程,會(huì)循環(huán)不斷的處理pending鏈表中的對(duì)象引用,執(zhí)行Cleaner的clean方法進(jìn)行相關(guān)清理工作。

所以當(dāng)DirectByteBuffer僅被Cleaner引用(即為虛引用)時(shí),其可以在任意GC時(shí)段被回收。當(dāng)DirectByteBuffer實(shí)例對(duì)象被回收時(shí),在Reference-Handler線程操作中,會(huì)調(diào)用Cleaner的clean方法根據(jù)創(chuàng)建Cleaner時(shí)傳入的Deallocator來(lái)進(jìn)行堆外內(nèi)存的釋放。

CAS相關(guān)
如下源代碼釋義所示,這部分主要為CAS相關(guān)操作的方法。
/**
* CAS
* @param o 包含要修改field的對(duì)象
* @param offset 對(duì)象中某field的偏移量
* @param expected 期望值
* @param update 更新值
* @return true | false
*/
public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object update);
public final native boolean compareAndSwapInt(Object o, long offset, int expected,int update);
public final native boolean compareAndSwapLong(Object o, long offset, long expected, long update);
什么是CAS? 即比較并替換,實(shí)現(xiàn)并發(fā)算法時(shí)常用到的一種技術(shù)。CAS操作包含三個(gè)操作數(shù)——內(nèi)存位置、預(yù)期原值及新值。執(zhí)行CAS操作的時(shí)候,將內(nèi)存位置的值與預(yù)期原值比較,如果相匹配,那么處理器會(huì)自動(dòng)將該位置值更新為新值,否則,處理器不做任何操作。我們都知道,CAS是一條CPU的原子指令(cmpxchg指令),不會(huì)造成所謂的數(shù)據(jù)不一致問(wèn)題,Unsafe提供的CAS方法(如compareAndSwapXXX)底層實(shí)現(xiàn)即為CPU指令cmpxchg。
典型應(yīng)用
CAS在java.util.concurrent.atomic相關(guān)類(lèi)、Java AQS、CurrentHashMap等實(shí)現(xiàn)上有非常廣泛的應(yīng)用。如下圖所示,AtomicInteger的實(shí)現(xiàn)中,靜態(tài)字段valueOffset即為字段value的內(nèi)存偏移地址,valueOffset的值在AtomicInteger初始化時(shí),在靜態(tài)代碼塊中通過(guò)Unsafe的objectFieldOffset方法獲取。在AtomicInteger中提供的線程安全方法中,通過(guò)字段valueOffset的值可以定位到AtomicInteger對(duì)象中value的內(nèi)存地址,從而可以根據(jù)CAS實(shí)現(xiàn)對(duì)value字段的原子操作。

下圖為某個(gè)AtomicInteger對(duì)象自增操作前后的內(nèi)存示意圖,對(duì)象的基地址baseAddress="0x110000",通過(guò)baseAddress+valueOffset得到value的內(nèi)存地址valueAddress="0x11000c";然后通過(guò)CAS進(jìn)行原子性的更新操作,成功則返回,否則繼續(xù)重試,直到更新成功為止。
線程調(diào)度
這部分,包括線程掛起、恢復(fù)、鎖機(jī)制等方法。
//取消阻塞線程
public native void unpark(Object thread);
//阻塞線程
public native void park(boolean isAbsolute, long time);
//獲得對(duì)象鎖(可重入鎖)
@Deprecated
public native void monitorEnter(Object o);
//釋放對(duì)象鎖
@Deprecated
public native void monitorExit(Object o);
//嘗試獲取對(duì)象鎖
@Deprecated
public native boolean tryMonitorEnter(Object o);
如上源碼說(shuō)明中,方法park、unpark即可實(shí)現(xiàn)線程的掛起與恢復(fù),將一個(gè)線程進(jìn)行掛起是通過(guò)park方法實(shí)現(xiàn)的,調(diào)用park方法后,線程將一直阻塞直到超時(shí)或者中斷等條件出現(xiàn);unpark可以終止一個(gè)掛起的線程,使其恢復(fù)正常。
典型應(yīng)用
Java鎖和同步器框架的核心類(lèi)AbstractQueuedSynchronizer,就是通過(guò)調(diào)用LockSupport.park()
和LockSupport.unpark()
實(shí)現(xiàn)線程的阻塞和喚醒的,而LockSupport的park、unpark方法實(shí)際是調(diào)用Unsafe的park、unpark方式來(lái)實(shí)現(xiàn)。
Class相關(guān)
此部分主要提供Class和它的靜態(tài)字段的操作相關(guān)方法,包含靜態(tài)字段內(nèi)存定位、定義類(lèi)、定義匿名類(lèi)、檢驗(yàn)&確保初始化等。
//獲取給定靜態(tài)字段的內(nèi)存地址偏移量,這個(gè)值對(duì)于給定的字段是唯一且固定不變的
public native long staticFieldOffset(Field f);
//獲取一個(gè)靜態(tài)類(lèi)中給定字段的對(duì)象指針
public native Object staticFieldBase(Field f);
//判斷是否需要初始化一個(gè)類(lèi),通常需要使用在獲取一個(gè)類(lèi)的靜態(tài)屬性的時(shí)候(因?yàn)橐粋€(gè)類(lèi)如果沒(méi)初始化,它的靜態(tài)屬性也不會(huì)初始化)。 此方法當(dāng)且僅當(dāng)ensureClassInitialized方法不生效的時(shí)候才返回false。
public native boolean shouldBeInitialized(Class> c);
//檢測(cè)給定的類(lèi)是否已經(jīng)初始化。通常需要使用在獲取一個(gè)類(lèi)的靜態(tài)屬性的時(shí)候(因?yàn)橐粋€(gè)類(lèi)如果沒(méi)初始化,它的靜態(tài)屬性也不會(huì)初始化)。
public native void ensureClassInitialized(Class> c);
//定義一個(gè)類(lèi),此方法會(huì)跳過(guò)JVM的所有安全檢查,默認(rèn)情況下,ClassLoader(類(lèi)加載器)和ProtectionDomain(保護(hù)域)實(shí)例來(lái)源于調(diào)用者
public native Class> defineClass(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain);
//定義一個(gè)匿名類(lèi)
public native Class> defineAnonymousClass(Class> hostClass, byte[] data, Object[] cpPatches);
典型應(yīng)用
從Java 8開(kāi)始,JDK使用invokedynamic及VM Anonymous Class結(jié)合來(lái)實(shí)現(xiàn)Java語(yǔ)言層面上的Lambda表達(dá)式。
invokedynamic: invokedynamic是Java 7為了實(shí)現(xiàn)在JVM上運(yùn)行動(dòng)態(tài)語(yǔ)言而引入的一條新的虛擬機(jī)指令,它可以實(shí)現(xiàn)在運(yùn)行期動(dòng)態(tài)解析出調(diào)用點(diǎn)限定符所引用的方法,然后再執(zhí)行該方法,invokedynamic指令的分派邏輯是由用戶設(shè)定的引導(dǎo)方法決定。
VM Anonymous Class:可以看做是一種模板機(jī)制,針對(duì)于程序動(dòng)態(tài)生成很多結(jié)構(gòu)相同、僅若干常量不同的類(lèi)時(shí),可以先創(chuàng)建包含常量占位符的模板類(lèi),而后通過(guò)Unsafe.defineAnonymousClass方法定義具體類(lèi)時(shí)填充模板的占位符生成具體的匿名類(lèi)。生成的匿名類(lèi)不顯式掛在任何ClassLoader下面,只要當(dāng)該類(lèi)沒(méi)有存在的實(shí)例對(duì)象、且沒(méi)有強(qiáng)引用來(lái)引用該類(lèi)的Class對(duì)象時(shí),該類(lèi)就會(huì)被GC回收。故而VM Anonymous Class相比于Java語(yǔ)言層面的匿名內(nèi)部類(lèi)無(wú)需通過(guò)ClassClassLoader進(jìn)行類(lèi)加載且更易回收。
在Lambda表達(dá)式實(shí)現(xiàn)中,通過(guò)invokedynamic指令調(diào)用引導(dǎo)方法生成調(diào)用點(diǎn),在此過(guò)程中,會(huì)通過(guò)ASM動(dòng)態(tài)生成字節(jié)碼,而后利用Unsafe的defineAnonymousClass方法定義實(shí)現(xiàn)相應(yīng)的函數(shù)式接口的匿名類(lèi),然后再實(shí)例化此匿名類(lèi),并返回與此匿名類(lèi)中函數(shù)式方法的方法句柄關(guān)聯(lián)的調(diào)用點(diǎn);而后可以通過(guò)此調(diào)用點(diǎn)實(shí)現(xiàn)調(diào)用相應(yīng)Lambda表達(dá)式定義邏輯的功能。下面以如下圖所示的Test類(lèi)來(lái)舉例說(shuō)明。

Test類(lèi)編譯后的class文件反編譯后的結(jié)果如下圖一所示(刪除了對(duì)本文說(shuō)明無(wú)意義的部分),我們可以從中看到main方法的指令實(shí)現(xiàn)、invokedynamic指令調(diào)用的引導(dǎo)方法BootstrapMethods、及靜態(tài)方法lambda$main$0
(實(shí)現(xiàn)了Lambda表達(dá)式中字符串打印邏輯)等。在引導(dǎo)方法執(zhí)行過(guò)程中,會(huì)通過(guò)Unsafe.defineAnonymousClass生成如下圖二所示的實(shí)現(xiàn)Consumer接口的匿名類(lèi)。其中,accept方法通過(guò)調(diào)用Test類(lèi)中的靜態(tài)方法lambda$main$0
來(lái)實(shí)現(xiàn)Lambda表達(dá)式中定義的邏輯。而后執(zhí)行語(yǔ)句consumer.accept("lambda")
其實(shí)就是調(diào)用下圖二所示的匿名類(lèi)的accept方法。

對(duì)象操作
此部分主要包含對(duì)象成員屬性相關(guān)操作及非常規(guī)的對(duì)象實(shí)例化方式等相關(guān)方法。
//返回對(duì)象成員屬性在內(nèi)存地址相對(duì)于此對(duì)象的內(nèi)存地址的偏移量
public native long objectFieldOffset(Field f);
//獲得給定對(duì)象的指定地址偏移量的值,與此類(lèi)似操作還有:getInt,getDouble,getLong,getChar等
public native Object getObject(Object o, long offset);
//給定對(duì)象的指定地址偏移量設(shè)值,與此類(lèi)似操作還有:putInt,putDouble,putLong,putChar等
public native void putObject(Object o, long offset, Object x);
//從對(duì)象的指定偏移量處獲取變量的引用,使用volatile的加載語(yǔ)義
public native Object getObjectVolatile(Object o, long offset);
//存儲(chǔ)變量的引用到對(duì)象的指定的偏移量處,使用volatile的存儲(chǔ)語(yǔ)義
public native void putObjectVolatile(Object o, long offset, Object x);
//有序、延遲版本的putObjectVolatile方法,不保證值的改變被其他線程立即看到。只有在field被volatile修飾符修飾時(shí)有效
public native void putOrderedObject(Object o, long offset, Object x);
//繞過(guò)構(gòu)造方法、初始化代碼來(lái)創(chuàng)建對(duì)象
public native Object allocateInstance(Class> cls) throws InstantiationException;
典型應(yīng)用
常規(guī)對(duì)象實(shí)例化方式:我們通常所用到的創(chuàng)建對(duì)象的方式,從本質(zhì)上來(lái)講,都是通過(guò)new機(jī)制來(lái)實(shí)現(xiàn)對(duì)象的創(chuàng)建。但是,new機(jī)制有個(gè)特點(diǎn)就是當(dāng)類(lèi)只提供有參的構(gòu)造函數(shù)且無(wú)顯示聲明無(wú)參構(gòu)造函數(shù)時(shí),則必須使用有參構(gòu)造函數(shù)進(jìn)行對(duì)象構(gòu)造,而使用有參構(gòu)造函數(shù)時(shí),必須傳遞相應(yīng)個(gè)數(shù)的參數(shù)才能完成對(duì)象實(shí)例化。
非常規(guī)的實(shí)例化方式:而Unsafe中提供allocateInstance方法,僅通過(guò)Class對(duì)象就可以創(chuàng)建此類(lèi)的實(shí)例對(duì)象,而且不需要調(diào)用其構(gòu)造函數(shù)、初始化代碼、JVM安全檢查等。它抑制修飾符檢測(cè),也就是即使構(gòu)造器是private修飾的也能通過(guò)此方法實(shí)例化,只需提類(lèi)對(duì)象即可創(chuàng)建相應(yīng)的對(duì)象。由于這種特性,allocateInstance在java.lang.invoke、Objenesis(提供繞過(guò)類(lèi)構(gòu)造器的對(duì)象生成方式)、Gson(反序列化時(shí)用到)中都有相應(yīng)的應(yīng)用。
如下圖所示,在Gson反序列化時(shí),如果類(lèi)有默認(rèn)構(gòu)造函數(shù),則通過(guò)反射調(diào)用默認(rèn)構(gòu)造函數(shù)創(chuàng)建實(shí)例,否則通過(guò)UnsafeAllocator來(lái)實(shí)現(xiàn)對(duì)象實(shí)例的構(gòu)造,UnsafeAllocator通過(guò)調(diào)用Unsafe的allocateInstance實(shí)現(xiàn)對(duì)象的實(shí)例化,保證在目標(biāo)類(lèi)無(wú)默認(rèn)構(gòu)造函數(shù)時(shí),反序列化不夠影響。

數(shù)組相關(guān)
這部分主要介紹與數(shù)據(jù)操作相關(guān)的arrayBaseOffset與arrayIndexScale這兩個(gè)方法,兩者配合起來(lái)使用,即可定位數(shù)組中每個(gè)元素在內(nèi)存中的位置。
//返回?cái)?shù)組中第一個(gè)元素的偏移地址
public native int arrayBaseOffset(Class> arrayClass);
//返回?cái)?shù)組中一個(gè)元素占用的大小
public native int arrayIndexScale(Class> arrayClass);
典型應(yīng)用
這兩個(gè)與數(shù)據(jù)操作相關(guān)的方法,在java.util.concurrent.atomic 包下的AtomicIntegerArray(可以實(shí)現(xiàn)對(duì)Integer數(shù)組中每個(gè)元素的原子性操作)中有典型的應(yīng)用,如下圖AtomicIntegerArray源碼所示,通過(guò)Unsafe的arrayBaseOffset、arrayIndexScale分別獲取數(shù)組首元素的偏移地址base及單個(gè)元素大小因子scale。后續(xù)相關(guān)原子性操作,均依賴于這兩個(gè)值進(jìn)行數(shù)組中元素的定位,如下圖二所示的getAndAdd方法即通過(guò)checkedByteOffset方法獲取某數(shù)組元素的偏移地址,而后通過(guò)CAS實(shí)現(xiàn)原子性操作。

內(nèi)存屏障
在Java 8中引入,用于定義內(nèi)存屏障(也稱內(nèi)存柵欄,內(nèi)存柵障,屏障指令等,是一類(lèi)同步屏障指令,是CPU或編譯器在對(duì)內(nèi)存隨機(jī)訪問(wèn)的操作中的一個(gè)同步點(diǎn),使得此點(diǎn)之前的所有讀寫(xiě)操作都執(zhí)行后才可以開(kāi)始執(zhí)行此點(diǎn)之后的操作),避免代碼重排序。
//內(nèi)存屏障,禁止load操作重排序。屏障前的load操作不能被重排序到屏障后,屏障后的load操作不能被重排序到屏障前
public native void loadFence();
//內(nèi)存屏障,禁止store操作重排序。屏障前的store操作不能被重排序到屏障后,屏障后的store操作不能被重排序到屏障前
public native void storeFence();
//內(nèi)存屏障,禁止load、store操作重排序
public native void fullFence();
典型應(yīng)用
在Java 8中引入了一種鎖的新機(jī)制——StampedLock,它可以看成是讀寫(xiě)鎖的一個(gè)改進(jìn)版本。StampedLock提供了一種樂(lè)觀讀鎖的實(shí)現(xiàn),這種樂(lè)觀讀鎖類(lèi)似于無(wú)鎖的操作,完全不會(huì)阻塞寫(xiě)線程獲取寫(xiě)鎖,從而緩解讀多寫(xiě)少時(shí)寫(xiě)線程“饑餓”現(xiàn)象。由于StampedLock提供的樂(lè)觀讀鎖不阻塞寫(xiě)線程獲取讀鎖,當(dāng)線程共享變量從主內(nèi)存load到線程工作內(nèi)存時(shí),會(huì)存在數(shù)據(jù)不一致問(wèn)題,所以當(dāng)使用StampedLock的樂(lè)觀讀鎖時(shí),需要遵從如下圖用例中使用的模式來(lái)確保數(shù)據(jù)的一致性。

如上圖用例所示計(jì)算坐標(biāo)點(diǎn)Point對(duì)象,包含點(diǎn)移動(dòng)方法move及計(jì)算此點(diǎn)到原點(diǎn)的距離的方法distanceFromOrigin。在方法distanceFromOrigin中,首先,通過(guò)tryOptimisticRead方法獲取樂(lè)觀讀標(biāo)記;然后從主內(nèi)存中加載點(diǎn)的坐標(biāo)值 (x,y);而后通過(guò)StampedLock的validate方法校驗(yàn)鎖狀態(tài),判斷坐標(biāo)點(diǎn)(x,y)從主內(nèi)存加載到線程工作內(nèi)存過(guò)程中,主內(nèi)存的值是否已被其他線程通過(guò)move方法修改,如果validate返回值為true,證明(x, y)的值未被修改,可參與后續(xù)計(jì)算;否則,需加悲觀讀鎖,再次從主內(nèi)存加載(x,y)的最新值,然后再進(jìn)行距離計(jì)算。其中,校驗(yàn)鎖狀態(tài)這步操作至關(guān)重要,需要判斷鎖狀態(tài)是否發(fā)生改變,從而判斷之前copy到線程工作內(nèi)存中的值是否與主內(nèi)存的值存在不一致。
下圖為StampedLock.validate方法的源碼實(shí)現(xiàn),通過(guò)鎖標(biāo)記與相關(guān)常量進(jìn)行位運(yùn)算、比較來(lái)校驗(yàn)鎖狀態(tài),在校驗(yàn)邏輯之前,會(huì)通過(guò)Unsafe的loadFence方法加入一個(gè)load內(nèi)存屏障,目的是避免上圖用例中步驟②和StampedLock.validate中鎖狀態(tài)校驗(yàn)運(yùn)算發(fā)生重排序?qū)е骆i狀態(tài)校驗(yàn)不準(zhǔn)確的問(wèn)題。

系統(tǒng)相關(guān)
這部分包含兩個(gè)獲取系統(tǒng)相關(guān)信息的方法。
//返回系統(tǒng)指針的大小。返回值為4(32位系統(tǒng))或8(64位系統(tǒng))。
public native int addressSize();
//內(nèi)存頁(yè)的大小,此值為2的冪次方。
public native int pageSize();
典型應(yīng)用
如下圖所示的代碼片段,為java.nio下的工具類(lèi)Bits中計(jì)算待申請(qǐng)內(nèi)存所需內(nèi)存頁(yè)數(shù)量的靜態(tài)方法,其依賴于Unsafe中pageSize方法獲取系統(tǒng)內(nèi)存頁(yè)大小實(shí)現(xiàn)后續(xù)計(jì)算邏輯。

結(jié)語(yǔ)
本文對(duì)Java中的sun.misc.Unsafe的用法及應(yīng)用場(chǎng)景進(jìn)行了基本介紹,我們可以看到Unsafe提供了很多便捷、有趣的API方法。即便如此,由于Unsafe中包含大量自主操作內(nèi)存的方法,如若使用不當(dāng),會(huì)對(duì)程序帶來(lái)許多不可控的災(zāi)難。因此對(duì)它的使用我們需要慎之又慎。
參考資料
OpenJDK Unsafe source
Java Magic. Part 4: sun.misc.Unsafe
JVM crashes at libjvm.so
Java中神奇的雙刃劍--Unsafe
JVM源碼分析之堆外內(nèi)存完全解讀
堆外內(nèi)存 之 DirectByteBuffer 詳解
《深入理解Java虛擬機(jī)(第2版)》
作者簡(jiǎn)介
璐璐,美團(tuán)點(diǎn)評(píng)Java開(kāi)發(fā)工程師。2017年加入美團(tuán)點(diǎn)評(píng),負(fù)責(zé)美團(tuán)點(diǎn)評(píng)境內(nèi)度假的后端開(kāi)發(fā)。
分享文章:【基本功】Java魔法類(lèi):Unsafe應(yīng)用解析
本文網(wǎng)址:http://www.dlmjj.cn/article/ppjjjd.html