各大框架都在使用的Unsafe類,到底有多神奇?
幾乎每個(gè)使用 Java開發(fā)的工具、軟件基礎(chǔ)設(shè)施、高性能開發(fā)庫(kù)都在底層使用了sun.misc.Unsafe,比如Netty、Cassandra、Hadoop、Kafka等。
Unsafe類在提升Java運(yùn)行效率,增強(qiáng)Java語(yǔ)言底層操作能力方面起了很大的作用。但Unsafe類在sun.misc包下,不屬于Java標(biāo)準(zhǔn)。
很早之前,在閱讀并發(fā)編程相關(guān)類的源碼時(shí),看到Unsafe類,產(chǎn)生了一個(gè)疑惑:既然是并發(fā)編程中用到的類,為什么命名為Unsafe呢?
深入了解之后才知道,這里的Unsafe并不是說(shuō)線程安全與否,而是指:該類對(duì)于普通的程序員來(lái)說(shuō)是”危險(xiǎn)“的,一般應(yīng)用開發(fā)者不會(huì)也不應(yīng)該用到此類。
因?yàn)閁nsafe類功能過(guò)于強(qiáng)大,提供了一些可以繞開JVM的更底層功能。它讓Java擁有了像C語(yǔ)言的指針一樣操作內(nèi)存空間的能力,能夠提升效率,但也帶來(lái)了指針的問(wèn)題。官方并不建議使用,也沒(méi)提供文檔支持,甚至計(jì)劃在高版本中去掉該類。
但對(duì)于開發(fā)者來(lái)說(shuō),了解該類提供的功能更有助于我們學(xué)習(xí)CAS、并發(fā)編程等相關(guān)的知識(shí),還是非常有必要學(xué)習(xí)和了解的。
Unsafe的構(gòu)造
Unsafe類是"final"的,不允許繼承,且構(gòu)造函數(shù)是private,使用了單例模式來(lái)通過(guò)一個(gè)靜態(tài)方法getUnsafe()來(lái)獲取。
????private?Unsafe()?{
????}
????@CallerSensitive
????public?static?Unsafe?getUnsafe()?{
????????Class?var0?=?Reflection.getCallerClass();
????????if?(!VM.isSystemDomainLoader(var0.getClassLoader()))?{
????????????throw?new?SecurityException("Unsafe");
????????}?else?{
????????????return?theUnsafe;
????????}
????}
在getUnsafe方法中對(duì)單例模式中的對(duì)象創(chuàng)建做了限制,如果是普通的調(diào)用會(huì)拋出一個(gè)SecurityException異常。只有由主類加載器加載的類才能調(diào)用這個(gè)方法。
那么,如何獲得Unsafe類的對(duì)象呢?通常采用反射機(jī)制:
public?static?Unsafe?getUnsafe()?throws?IllegalAccessException?{
??Field?unsafeField?=?Unsafe.class.getDeclaredFields()[0];
??unsafeField.setAccessible(true);
??return?(Unsafe)?unsafeField.get(null);
}
當(dāng)獲得Unsafe對(duì)象之后,就可以”為所欲為“了。下面就來(lái)看看,通過(guò)Unsafe方法,我們可以做些什么。
Unsafe的主要功能
可先從根據(jù)下圖從整體上了解一下Unsafe提供的功能:

下面挑選重要的功能進(jìn)行講解。
一、內(nèi)存管理
Unsafe的內(nèi)存管理功能主要包括:普通讀寫、volatile讀寫、有序?qū)懭?、直接操作?nèi)存等分配內(nèi)存與釋放內(nèi)存的功能。
普通讀寫
Unsafe可以讀寫一個(gè)類的屬性,即便這個(gè)屬性是私有的,也可以對(duì)這個(gè)屬性進(jìn)行讀寫。
//?獲取內(nèi)存地址指向的整數(shù)
public?native?int?getInt(Object?var1,?long?var2);
//?將整數(shù)寫入指定內(nèi)存地址
public?native?void?putInt(Object?var1,?long?var2,?int?var4);
getInt用于從對(duì)象的指定偏移地址處讀取一個(gè)int。putInt用于在對(duì)象指定偏移地址處寫入一個(gè)int。其他原始類型也提供有對(duì)應(yīng)的方法。
另外,Unsafe的getByte、putByte方法提供了直接在一個(gè)地址上進(jìn)行讀寫的功能。
volatile讀寫
普通的讀寫無(wú)法保證可見(jiàn)性和有序性,而volatile讀寫就可以保證可見(jiàn)性和有序性。
//?獲取內(nèi)存地址指向的整數(shù),并支持volatile語(yǔ)義
public?native?int?getIntVolatile(Object?var1,?long?var2);
//?將整數(shù)寫入指定內(nèi)存地址,并支持volatile語(yǔ)義
public?native?void?putIntVolatile(Object?var1,?long?var2,?int?var4);
volatile讀寫要保證可見(jiàn)性和有序性,相對(duì)普通讀寫更加昂貴。
有序?qū)懭?/h3>
有序?qū)懭胫槐WC寫入的有序性,不保證可見(jiàn)性,就是說(shuō)一個(gè)線程的寫入不保證其他線程立馬可見(jiàn)。
//?將整數(shù)寫入指定內(nèi)存地址、有序或者有延遲的方法
public?native?void?putOrderedInt(Object?var1,?long?var2,?int?var4);
而與volatile寫入相比putOrderedXX寫入代價(jià)相對(duì)較低,putOrderedXX寫入不保證可見(jiàn)性,但是保證有序性,所謂有序性,就是保證指令不會(huì)重排序。
直接操作內(nèi)存
Unsafe提供了直接操作內(nèi)存的能力:
//?分配內(nèi)存
public?native?long?allocateMemory(long?var1);
//?重新分配內(nèi)存
public?native?long?reallocateMemory(long?var1,?long?var3);
//?內(nèi)存初始化
public?native?void?setMemory(long?var1,?long?var3,?byte?var5);
//?內(nèi)存復(fù)制
public?native?void?copyMemory(Object?var1,?long?var2,?Object?var4,?long?var5,?long?var7);
//?清除內(nèi)存
public?native?void?freeMemory(long?var1);
對(duì)應(yīng)操作內(nèi)存,也提供了一些獲取內(nèi)存信息的方法:
//?獲取內(nèi)存地址
public?native?long?getAddress(long?var1);
public?native?int?addressSize();
public?native?int?pageSize();
值得注意的是:利用copyMemory方法可以實(shí)現(xiàn)一個(gè)通用的對(duì)象拷貝方法,無(wú)需再對(duì)每一個(gè)對(duì)象都實(shí)現(xiàn)clone方法,但只能做到對(duì)象淺拷貝。
二、非常規(guī)對(duì)象實(shí)例化
通常,我們通過(guò)new或反射來(lái)實(shí)例化對(duì)象,而Unsafe類提供的allocateInstance方法,可以直接生成對(duì)象實(shí)例,且無(wú)需調(diào)用構(gòu)造方法和其他初始化方法。
這在對(duì)象反序列化的時(shí)候會(huì)很有用,能夠重建和設(shè)置final字段,而不需要調(diào)用構(gòu)造方法。
//?直接生成對(duì)象實(shí)例,不會(huì)調(diào)用這個(gè)實(shí)例的構(gòu)造方法
public?native?Object?allocateInstance(Class>?var1)?throws?InstantiationException;
三、類加載
通過(guò)以下方法,可以實(shí)現(xiàn)類的定義、創(chuàng)建等操作。
//?方法定義一個(gè)類,用于動(dòng)態(tài)地創(chuàng)建類
public?native?Class>?defineClass(String?var1,?byte[]?var2,?int?var3,?int?var4,?ClassLoader?var5,?ProtectionDomain?var6);
//??動(dòng)態(tài)的創(chuàng)建一個(gè)匿名內(nèi)部類
public?native?Class>?defineAnonymousClass(Class>?var1,?byte[]?var2,?Object[]?var3);
//?判斷是否需要初始化一個(gè)類
public?native?boolean?shouldBeInitialized(Class>?var1);
//?保證已經(jīng)初始化過(guò)一個(gè)類
public?native?void?ensureClassInitialized(Class>?var1);
四、偏移量相關(guān)
Unsafe提供以下方法獲取對(duì)象的指針,通過(guò)對(duì)指針進(jìn)行偏移,不僅可以直接修改指針指向的數(shù)據(jù)(即使它們是私有的),甚至可以找到JVM已經(jīng)認(rèn)定為垃圾、可以進(jìn)行回收的對(duì)象。
//?獲取靜態(tài)屬性Field在對(duì)象中的偏移量,讀寫靜態(tài)屬性時(shí)必須獲取其偏移量
public?native?long?staticFieldOffset(Field?var1);
//?獲取非靜態(tài)屬性Field在對(duì)象實(shí)例中的偏移量,讀寫對(duì)象的非靜態(tài)屬性時(shí)會(huì)用到這個(gè)偏移量
public?native?long?objectFieldOffset(Field?var1);
//?返回Field所在的對(duì)象
public?native?Object?staticFieldBase(Field?var1);
//?返回?cái)?shù)組中第一個(gè)元素實(shí)際地址相對(duì)整個(gè)數(shù)組對(duì)象的地址的偏移量
public?native?int?arrayBaseOffset(Class>?var1);
//?計(jì)算數(shù)組中第一個(gè)元素所占用的內(nèi)存空間
public?native?int?arrayIndexScale(Class>?var1);
五、數(shù)組操作
數(shù)組操作提供了以下方法:
//?獲取數(shù)組第一個(gè)元素的偏移地址
public?native?int?arrayBaseOffset(Class>?var1);
//?獲取數(shù)組中元素的增量地址
public?native?int?arrayIndexScale(Class>?var1);
arrayBaseOffset與arrayIndexScale配合起來(lái)使用,就可以定位數(shù)組中每個(gè)元素在內(nèi)存中的位置。
由于Java的數(shù)組最大值為Integer.MAX_VALUE,使用Unsafe類的內(nèi)存分配方法可以實(shí)現(xiàn)超大數(shù)組。實(shí)際上這樣的數(shù)據(jù)就可以認(rèn)為是C數(shù)組,因此需要注意在合適的時(shí)間釋放內(nèi)存。
六、線程調(diào)度
線程調(diào)度相關(guān)方法如下:
//?喚醒線程
public?native?void?unpark(Object?var1);
//?掛起線程
public?native?void?park(boolean?var1,?long?var2);
//?用于加鎖,已廢棄
public?native?void?monitorEnter(Object?var1);
//?用于加鎖,已廢棄
public?native?void?monitorExit(Object?var1);
//?用于加鎖,已廢棄
public?native?boolean?tryMonitorEnter(Object?var1);
通過(guò)park方法將線程進(jìn)行掛起, 線程將一直阻塞到超時(shí)或中斷條件出現(xiàn)。unpark方法可以終止一個(gè)掛起的線程,使其恢復(fù)正常。
整個(gè)并發(fā)框架中對(duì)線程的掛起操作被封裝在LockSupport類中,LockSupport類中有各種版本pack方法,但最終都調(diào)用了Unsafe.park()方法。
七、CAS操作
Unsafe類的CAS操作可能是使用最多的方法。它為Java的鎖機(jī)制提供了一種新的解決辦法,比如AtomicInteger等類都是通過(guò)該方法來(lái)實(shí)現(xiàn)的。compareAndSwap方法是原子的,可以避免繁重的鎖機(jī)制,提高代碼效率。
public?final?native?boolean?compareAndSwapObject(Object?var1,?long?var2,?Object?var4,?Object?var5);
public?final?native?boolean?compareAndSwapInt(Object?var1,?long?var2,?int?var4,?int?var5);
public?final?native?boolean?compareAndSwapLong(Object?var1,?long?var2,?long?var4,?long?var6);
CAS一般用于樂(lè)觀鎖,它在Java中有廣泛的應(yīng)用,ConcurrentHashMap,ConcurrentLinkedQueue中都有用到CAS來(lái)實(shí)現(xiàn)樂(lè)觀鎖。
八、內(nèi)存屏障
JDK8新引入了用于定義內(nèi)存屏障、避免代碼重排的方法:
//?保證在這個(gè)屏障之前的所有讀操作都已經(jīng)完成
public?native?void?loadFence();
//?保證在這個(gè)屏障之前的所有寫操作都已經(jīng)完成
public?native?void?storeFence();
//?保證在這個(gè)屏障之前的所有讀寫操作都已經(jīng)完成
public?native?void?fullFence();
九、其他
當(dāng)然,Unsafe類中還提供了大量其他的方法,比如上面提到的CAS操作,以AtomicInteger為例,當(dāng)我們調(diào)用getAndIncrement、getAndDecrement等方法時(shí),本質(zhì)上調(diào)用的就是Unsafe的getAndAddInt方法。
public?final?int?getAndIncrement()?{
????return?unsafe.getAndAddInt(this,?valueOffset,?1);
}
public?final?int?getAndDecrement()?{
????return?unsafe.getAndAddInt(this,?valueOffset,?-1);
}
在實(shí)踐的過(guò)程中,如果閱讀其他框架或類庫(kù)實(shí)現(xiàn),當(dāng)發(fā)現(xiàn)用到Unsafe類,可對(duì)照該類的整體功能,結(jié)合應(yīng)用場(chǎng)景進(jìn)行分析,即可大概了解其功能。
小結(jié)
經(jīng)過(guò)本文的分析,想必大家在閱讀源碼時(shí),再遇到Unsafe類的調(diào)用,一定大概猜出它是用來(lái)干什么的。使用Unsafe類的主要目的大多數(shù)情況下是為了提升運(yùn)行效率、增強(qiáng)功能。但同時(shí)也面臨著出錯(cuò)、內(nèi)存管理等風(fēng)險(xiǎn)。只有深入了解,且有必要的情況下才建議使用。
完
往期推薦

Java反射到底慢在哪?

前OPPO 員工控訴:離職時(shí)績(jī)效為B,離職后被悄悄改成D,就為了少發(fā)年終獎(jiǎng)!

同事多線程使用不當(dāng)導(dǎo)致OOM,被我懟了一頓
有道無(wú)術(shù),術(shù)可成;有術(shù)無(wú)道,止于術(shù)
歡迎大家關(guān)注Java之道公眾號(hào)
好文章,我在看??
