JAVA Unsafe类
Unsafe為我們提供了訪問底層的機制,這種機制僅供java核心類庫使用,而不應該被普通用戶使用。但是,為了更好地了解java的生態體系,我們應該去學習它,去了解它,不求深入到底層的C/C++代碼,但求能了解它的基本功能。
一、獲取Unsafe的實例
查看Unsafe的源碼我們會發現它提供了一個getUnsafe()的靜態方法。
@CallerSensitive public static Unsafe getUnsafe() {Class var0 = Reflection.getCallerClass();if (!VM.isSystemDomainLoader(var0.getClassLoader())) {throw new SecurityException("Unsafe");} else {return theUnsafe;} }但是,如果直接調用這個方法會拋出一個SecurityException異常,這是因為Unsafe僅供java內部類使用,外部類不應該使用它。
public class GetUnsafe {public static void main(String[] args) {Unsafe unsafe = Unsafe.getUnsafe();System.out.println(unsafe);} }
那么,如何獲取該類實例?反射!,查看源碼我們發現它有一個屬性叫theUnsafe,我們直接通過反射拿到它即可。
二、Unsafe類作用
那么問題來了,拼命拿到這個類有啥用?
- 假如我們有一個簡單的類如下
- 實例化該類
是的,沒看錯,使用unsafe,可以突破任何修飾符的限制,直接修改字段屬性。(雖然通過原來的類經過反射也能修改private屬性,但是unsafe顯然簡單得多)
- 使用Unsafe的putXXX()方法,我們可以修改任意私有字段的值。
我們知道如果代碼拋出了checked異常,要不就使用try…catch捕獲它,要不就在方法簽名上定義這個異常,但是,通過Unsafe我們可以拋出一個checked異常,同時卻不用捕獲或在方法簽名上定義它。
@Testpublic void throwNormalException() throws IOException {// 使用正常方式拋出IOException需要定義在方法簽名上往外拋throw new IOException();} ----------------------------------------------------------@Testpublic void throwExceptionByUnsafe() throws NoSuchFieldException, IllegalAccessException {Field f = Unsafe.class.getDeclaredField("theUnsafe");f.setAccessible(true);Unsafe unsafe = (Unsafe) f.get(null);// 使用Unsafe拋出異常不需要定義在方法簽名上往外拋unsafe.throwException(new IOException());}這部分主要包含堆外內存的分配、拷貝、釋放、給定地址值操作等方法。
//分配內存, 相當于C++的malloc函數 public native long allocateMemory(long bytes); //擴充內存 public native long reallocateMemory(long address, long bytes); //釋放內存 public native void freeMemory(long address); //在給定的內存塊中設置值 public native void setMemory(Object o, long offset, long bytes, byte value); //內存拷貝 public native void copyMemory(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes); //獲取給定地址值,忽略修飾限定符的訪問限制。與此類似操作還有: getInt,getDouble,getLong,getChar等 public native Object getObject(Object o, long offset); //為給定地址設置值,忽略修飾限定符的訪問限制,與此類似操作還有: putInt,putDouble,putLong,putChar等 public native void putObject(Object o, long offset, Object x); //獲取給定地址的byte類型的值(當且僅當該內存地址為allocateMemory分配時,此方法結果為確定的) public native byte getByte(long address); //為給定地址設置byte類型的值(當且僅當該內存地址為allocateMemory分配時,此方法結果才是確定的) public native void putByte(long address, byte x);- 以使用堆外內存為例
如果進程在運行過程中JVM上的內存不足了,會導致頻繁的進行GC。理想情況下,我們可以考慮使用堆外內存,這是一塊不受JVM管理的內存。使用Unsafe的allocateMemory()我們可以直接在堆外分配內存,這可能非常有用,但我們要記住,這個內存不受JVM管理,因此我們要調用freeMemory()方法手動釋放它。假設我們要在堆外創建一個巨大的int數組,我們可以使用allocateMemory()方法來實現:
- 堆外數組類
- 測試
我們知道CAS是實現并發算法時常用到的一種技術。它的操作包含三個操作數——內存位置、預期原值及新值。執行CAS操作的時候,將內存位置的值與預期原值比較,如果相匹配,那么處理器會自動將該位置值更新為新值,否則,處理器不做任何操作。我們都知道,CAS是一條CPU的原子指令(cmpxchg指令),不會造成所謂的數據不一致問題,Unsafe提供的CAS方法(如compareAndSwapXXX)底層實現即為CPU指令cmpxchg。
- 典型應用
CAS在java.util.concurrent.atomic相關類、Java AQS、CurrentHashMap等實現上有非常廣泛的應用。如下圖所示,AtomicInteger的實現中:
- 靜態字段valueOffset即為字段value的內存偏移地址;
- valueOffset的值在AtomicInteger初始化時,在靜態代碼塊中通過Unsafe的objectFieldOffset方法獲取。
- 在AtomicInteger中提供的線程安全方法中,通過字段valueOffset的值可以定位到AtomicInteger對象中value的內存地址,從而可以根據CAS實現對value字段的原子操作。
下圖為某個AtomicInteger對象自增操作前后的內存示意圖,對象的基地址baseAddress=“0x110000”,通過baseAddress+valueOffset得到value的內存地址valueAddress=“0x11000c”;然后通過CAS進行原子性的更新操作,成功則返回,否則繼續重試,直到更新成功為止。
- 基于Unsafe的compareAndSwapInt()方法構建線程安全的計數器
- 計數類
- 測試
方法park、unpark即可實現線程的掛起與恢復,將一個線程進行掛起是通過park方法實現的,調用park方法后,線程將一直阻塞直到超時或者中斷等條件出現;unpark可以終止一個掛起的線程,使其恢復正常。
- 典型應用
Java鎖和同步器框架的核心類AbstractQueuedSynchronizer,就是通過調用LockSupport.park()和LockSupport.unpark()實現線程的阻塞和喚醒的,而LockSupport的park、unpark方法實際是調用Unsafe的park、unpark方式來實現。
參考文章
參考文章
總結
以上是生活随笔為你收集整理的JAVA Unsafe类的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2021中国移动经济发展报告
- 下一篇: 2020年母婴消费洞察报告