Java的Unsafe類


本文轉載自:https://www.cnblogs.com/pkufork/p/java_unsafe.html

 

最近在看Java並發包的源碼,發現了神奇的Unsafe類,仔細研究了一下,在這里跟大家分享一下。

Unsafe類是在sun.misc包下,不屬於Java標准。但是很多Java的基礎類庫,包括一些被廣泛使用的高性能開發庫都是基於Unsafe類開發的,比如Netty、Cassandra、Hadoop、Kafka等。Unsafe類在提升Java運行效率,增強Java語言底層操作能力方面起了很大的作用。

Unsafe類使Java擁有了像C語言的指針一樣操作內存空間的能力,同時也帶來了指針的問題。過度的使用Unsafe類會使得出錯的幾率變大,因此Java官方並不建議使用的,官方文檔也幾乎沒有。Oracle正在計划從Java 9中去掉Unsafe類,如果真是如此影響就太大了。

通常我們最好也不要使用Unsafe類,除非有明確的目的,並且也要對它有深入的了解才行。要想使用Unsafe類需要用一些比較tricky的辦法。Unsafe類使用了單例模式,需要通過一個靜態方法getUnsafe()來獲取。但Unsafe類做了限制,如果是普通的調用的話,它會拋出一個SecurityException異常;只有由主類加載器加載的類才能調用這個方法。其源碼如下:

復制代碼
1 public static Unsafe getUnsafe() {
2     Class var0 = Reflection.getCallerClass();
3     if(!VM.isSystemDomainLoader(var0.getClassLoader())) {
4         throw new SecurityException("Unsafe");
5     } else {
6         return theUnsafe;
7     }
8 }
復制代碼

 

網上也有一些辦法來用主類加載器加載用戶代碼,比如設置bootclasspath參數。但更簡單方法是利用Java反射,方法如下:

1 Field f = Unsafe.class.getDeclaredField("theUnsafe");
2 f.setAccessible(true);
3 Unsafe unsafe = (Unsafe) f.get(null);

 

獲取到Unsafe實例之后,我們就可以為所欲為了。Unsafe類提供了以下這些功能:

一、內存管理包括分配內存、釋放內存等。

該部分包括了allocateMemory(分配內存)、reallocateMemory(重新分配內存)、copyMemory(拷貝內存)、freeMemory(釋放內存 )、getAddress(獲取內存地址)、addressSize、pageSize、getInt(獲取內存地址指向的整數)、getIntVolatile(獲取內存地址指向的整數,並支持volatile語義)、putInt(將整數寫入指定內存地址)、putIntVolatile(將整數寫入指定內存地址,並支持volatile語義)、putOrderedInt(將整數寫入指定內存地址、有序或者有延遲的方法)等方法。getXXX和putXXX包含了各種基本類型的操作。

利用copyMemory方法,我們可以實現一個通用的對象拷貝方法,無需再對每一個對象都實現clone方法,當然這通用的方法只能做到對象淺拷貝。

二、非常規的對象實例化。

allocateInstance()方法提供了另一種創建實例的途徑。通常我們可以用new或者反射來實例化對象,使用allocateInstance()方法可以直接生成對象實例,且無需調用構造方法和其它初始化方法。

這在對象反序列化的時候會很有用,能夠重建和設置final字段,而不需要調用構造方法。

三、操作類、對象、變量。

這部分包括了staticFieldOffset(靜態域偏移)、defineClass(定義類)、defineAnonymousClass(定義匿名類)、ensureClassInitialized(確保類初始化)、objectFieldOffset(對象域偏移)等方法。

通過這些方法我們可以獲取對象的指針,通過對指針進行偏移,我們不僅可以直接修改指針指向的數據(即使它們是私有的),甚至可以找到JVM已經認定為垃圾、可以進行回收的對象。

四、數組操作。

這部分包括了arrayBaseOffset(獲取數組第一個元素的偏移地址)、arrayIndexScale(獲取數組中元素的增量地址)等方法。arrayBaseOffset與arrayIndexScale配合起來使用,就可以定位數組中每個元素在內存中的位置。

由於Java的數組最大值為Integer.MAX_VALUE,使用Unsafe類的內存分配方法可以實現超大數組。實際上這樣的數據就可以認為是C數組,因此需要注意在合適的時間釋放內存。

五、多線程同步。包括鎖機制、CAS操作等。

這部分包括了monitorEnter、tryMonitorEnter、monitorExit、compareAndSwapInt、compareAndSwap等方法。

其中monitorEnter、tryMonitorEnter、monitorExit已經被標記為deprecated,不建議使用。

Unsafe類的CAS操作可能是用的最多的,它為Java的鎖機制提供了一種新的解決辦法,比如AtomicInteger等類都是通過該方法來實現的。compareAndSwap方法是原子的,可以避免繁重的鎖機制,提高代碼效率。這是一種樂觀鎖,通常認為在大部分情況下不出現競態條件,如果操作失敗,會不斷重試直到成功。

六、掛起與恢復。

這部分包括了park、unpark等方法。

將一個線程進行掛起是通過park方法實現的,調用 park后,線程將一直阻塞直到超時或者中斷等條件出現。unpark可以終止一個掛起的線程,使其恢復正常。整個並發框架中對線程的掛起操作被封裝在 LockSupport類中,LockSupport類中有各種版本pack方法,但最終都調用了Unsafe.park()方法。

七、內存屏障。

這部分包括了loadFence、storeFence、fullFence等方法。這是在Java 8新引入的,用於定義內存屏障,避免代碼重排序。

loadFence() 表示該方法之前的所有load操作在內存屏障之前完成。同理storeFence()表示該方法之前的所有store操作在內存屏障之前完成。fullFence()表示該方法之前的所有load、store操作在內存屏障之前完成。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM