1.Unsafe類簡介
Unsafe類是在sun.misc包下,不屬于Java標(biāo)準(zhǔn)底哗。但是很多Java的基礎(chǔ)類庫默伍,包括一些被廣泛使用的高性能開發(fā)庫都是基于Unsafe類開發(fā)的邮利,比如Netty芹缔、Cassandra、Hadoop、Kafka等。Unsafe類在提升Java運(yùn)行效率宁炫,增強(qiáng)Java語言底層操作能力方面起了很大的作用。
Unsafe類使Java擁有了像C語言的指針一樣操作內(nèi)存空間的能力击吱,同時也帶來了指針的問題淋淀。過度的使用Unsafe類會使得出錯的幾率變大遥昧,因此Java官方并不建議使用的覆醇,官方文檔也幾乎沒有。
Unsafe類包路徑變化
1.8之前Unsafe的包路徑為:
package sun.misc;
而到了java9炭臭,它的包路徑改成了下面這個永脓,說明這個類已經(jīng)開放使用。
package jdk.internal.misc;
- 獲取Unsafe實(shí)例方式
1.8之前Unsafe是不公開的類鞋仍,只能通過反射或者使用系統(tǒng)類加載器使用常摧,利用反射的使用方式如下。這里不再詳細(xì)分析威创。
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
而到了java9落午,Unsafe包含了一個靜態(tài)方法,可以直接拿到theUnsafe對象肚豺。如下溃斋。
/**
* 給調(diào)用者提供執(zhí)行不安全操作的能力
*/
public static Unsafe getUnsafe() {
return theUnsafe;
}
2.Unsafe類功能分類
2.1 內(nèi)存管理。包括分配內(nèi)存吸申、釋放內(nèi)存等梗劫。
該部分包括了allocateMemory(分配內(nèi)存)、reallocateMemory(重新分配內(nèi)存)截碴、copyMemory(拷貝內(nèi)存)梳侨、freeMemory(釋放內(nèi)存 )、getAddress(獲取內(nèi)存地址)日丹、addressSize走哺、pageSize、getInt(獲取內(nèi)存地址指向的整數(shù))哲虾、getIntVolatile(獲取內(nèi)存地址指向的整數(shù)割坠,并支持volatile語義)、putInt(將整數(shù)寫入指定內(nèi)存地址)妒牙、putIntVolatile(將整數(shù)寫入指定內(nèi)存地址彼哼,并支持volatile語義)、putOrderedInt(將整數(shù)寫入指定內(nèi)存地址湘今、有序或者有延遲的方法)等方法敢朱。getXXX和putXXX包含了各種基本類型的操作。
利用copyMemory方法,我們可以實(shí)現(xiàn)一個通用的對象拷貝方法拴签,無需再對每一個對象都實(shí)現(xiàn)clone方法孝常,當(dāng)然這通用的方法只能做到對象淺拷貝。
2.2 非常規(guī)的對象實(shí)例化蚓哩。
allocateInstance()方法提供了另一種創(chuàng)建實(shí)例的途徑构灸。通常我們可以用new或者反射來實(shí)例化對象,使用allocateInstance()方法可以直接生成對象實(shí)例岸梨,且無需調(diào)用構(gòu)造方法和其它初始化方法喜颁。
這在對象反序列化的時候會很有用,能夠重建和設(shè)置final字段曹阔,而不需要調(diào)用構(gòu)造方法半开。
2.3 操作類、對象赃份、變量寂拆。
這部分包括了staticFieldOffset(靜態(tài)域偏移)、defineClass(定義類)抓韩、defineAnonymousClass(定義匿名類)纠永、ensureClassInitialized(確保類初始化)、objectFieldOffset(對象域偏移)等方法谒拴。
通過這些方法我們可以獲取對象的指針尝江,通過對指針進(jìn)行偏移,我們不僅可以直接修改指針指向的數(shù)據(jù)(即使它們是私有的)彪薛,甚至可以找到JVM已經(jīng)認(rèn)定為垃圾茂装、可以進(jìn)行回收的對象。
2.4 數(shù)組操作善延。
這部分包括了arrayBaseOffset(獲取數(shù)組第一個元素的偏移地址)少态、arrayIndexScale(獲取數(shù)組中元素的增量地址)等方法。arrayBaseOffset與arrayIndexScale配合起來使用易遣,就可以定位數(shù)組中每個元素在內(nèi)存中的位置彼妻。
由于Java的數(shù)組最大值為Integer.MAX_VALUE,使用Unsafe類的內(nèi)存分配方法可以實(shí)現(xiàn)超大數(shù)組豆茫。實(shí)際上這樣的數(shù)據(jù)就可以認(rèn)為是C數(shù)組侨歉,因此需要注意在合適的時間釋放內(nèi)存。
2.5 多線程同步揩魂。包括鎖機(jī)制幽邓、CAS操作等。
這部分包括了monitorEnter火脉、tryMonitorEnter牵舵、monitorExit柒啤、compareAndSwapInt、compareAndSwap等方法畸颅。
其中monitorEnter担巩、tryMonitorEnter、monitorExit已經(jīng)被標(biāo)記為deprecated没炒,不建議使用涛癌。
Unsafe類的CAS操作可能是用的最多的,它為Java的鎖機(jī)制提供了一種新的解決辦法送火,比如AtomicInteger等類都是通過該方法來實(shí)現(xiàn)的拳话。compareAndSwap方法是原子的,可以避免繁重的鎖機(jī)制漾脂,提高代碼效率假颇。這是一種樂觀鎖胚鸯,通常認(rèn)為在大部分情況下不出現(xiàn)競態(tài)條件骨稿,如果操作失敗,會不斷重試直到成功姜钳。
2.6 掛起與恢復(fù)坦冠。
這部分包括了park、unpark等方法哥桥。
將一個線程進(jìn)行掛起是通過park方法實(shí)現(xiàn)的辙浑,調(diào)用 park后,線程將一直阻塞直到超時或者中斷等條件出現(xiàn)拟糕。unpark可以終止一個掛起的線程判呕,使其恢復(fù)正常。整個并發(fā)框架中對線程的掛起操作被封裝在 LockSupport類中送滞,LockSupport類中有各種版本pack方法侠草,但最終都調(diào)用了Unsafe.park()方法。
2.7 內(nèi)存屏障犁嗅。
這部分包括了loadFence边涕、storeFence、fullFence等方法褂微。這是在Java 8新引入的功蜓,用于定義內(nèi)存屏障,避免代碼重排序宠蚂。
loadFence() 表示該方法之前的所有l(wèi)oad操作在內(nèi)存屏障之前完成式撼。同理storeFence()表示該方法之前的所有store操作在內(nèi)存屏障之前完成。fullFence()表示該方法之前的所有l(wèi)oad求厕、store操作在內(nèi)存屏障之前完成著隆。
3.ConcurrentHashMap中使用Unsafe
// Unsafe mechanics
private static final Unsafe U = Unsafe.getUnsafe();
private static final long SIZECTL;
private static final long TRANSFERINDEX;
private static final long BASECOUNT;
private static final long CELLSBUSY;
private static final long CELLVALUE;
private static final int ABASE;
private static final int ASHIFT;
static {
- 獲取ConcurrentHashMap類的域變量sizeCtl transferIndex等
SIZECTL = U.objectFieldOffset
(ConcurrentHashMap.class, "sizeCtl");
TRANSFERINDEX = U.objectFieldOffset
(ConcurrentHashMap.class, "transferIndex");
BASECOUNT = U.objectFieldOffset
(ConcurrentHashMap.class, "baseCount");
CELLSBUSY = U.objectFieldOffset
(ConcurrentHashMap.class, "cellsBusy");
CELLVALUE = U.objectFieldOffset
(CounterCell.class, "value");
- 獲取ConcurrentHashMap類Node[]數(shù)組第一個元素的偏移地址ABASE叠洗,獲取數(shù)組中元素的增量地址scale
ABASE = U.arrayBaseOffset(Node[].class);
int scale = U.arrayIndexScale(Node[].class);
if ((scale & (scale - 1)) != 0)
throw new Error("array index scale not a power of two");
ASHIFT = 31 - Integer.numberOfLeadingZeros(scale);
// Reduce the risk of rare disastrous classloading in first call to
// LockSupport.park: https://bugs.openjdk.java.net/browse/JDK-8074773
Class<?> ensureLoaded = LockSupport.class;
}