本文轉載自:https://www.cnblogs.com/pkufork/p/java_unsafe.htmlhtml
最近在看Java併發包的源碼,發現了神奇的Unsafe類,仔細研究了一下,在這裏跟你們分享一下。java
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類提供瞭如下這些功能:oop
1、內存管理。包括分配內存、釋放內存等。性能
該部分包括了allocateMemory(分配內存)、reallocateMemory(從新分配內存)、copyMemory(拷貝內存)、freeMemory(釋放內存 )、getAddress(獲取內存地址)、addressSize、pageSize、getInt(獲取內存地址指向的整數)、getIntVolatile(獲取內存地址指向的整數,並支持volatile語義)、putInt(將整數寫入指定內存地址)、putIntVolatile(將整數寫入指定內存地址,並支持volatile語義)、putOrderedInt(將整數寫入指定內存地址、有序或者有延遲的方法)等方法。getXXX和putXXX包含了各類基本類型的操做。spa
利用copyMemory方法,咱們能夠實現一個通用的對象拷貝方法,無需再對每個對象都實現clone方法,固然這通用的方法只能作到對象淺拷貝。線程
2、很是規的對象實例化。
allocateInstance()方法提供了另外一種建立實例的途徑。一般咱們能夠用new或者反射來實例化對象,使用allocateInstance()方法能夠直接生成對象實例,且無需調用構造方法和其它初始化方法。
這在對象反序列化的時候會頗有用,可以重建和設置final字段,而不須要調用構造方法。
3、操做類、對象、變量。
這部分包括了staticFieldOffset(靜態域偏移)、defineClass(定義類)、defineAnonymousClass(定義匿名類)、ensureClassInitialized(確保類初始化)、objectFieldOffset(對象域偏移)等方法。
經過這些方法咱們能夠獲取對象的指針,經過對指針進行偏移,咱們不只能夠直接修改指針指向的數據(即便它們是私有的),甚至能夠找到JVM已經認定爲垃圾、能夠進行回收的對象。
4、數組操做。
這部分包括了arrayBaseOffset(獲取數組第一個元素的偏移地址)、arrayIndexScale(獲取數組中元素的增量地址)等方法。arrayBaseOffset與arrayIndexScale配合起來使用,就能夠定位數組中每一個元素在內存中的位置。
因爲Java的數組最大值爲Integer.MAX_VALUE,使用Unsafe類的內存分配方法能夠實現超大數組。實際上這樣的數據就能夠認爲是C數組,所以須要注意在合適的時間釋放內存。
5、多線程同步。包括鎖機制、CAS操做等。
這部分包括了monitorEnter、tryMonitorEnter、monitorExit、compareAndSwapInt、compareAndSwap等方法。
其中monitorEnter、tryMonitorEnter、monitorExit已經被標記爲deprecated,不建議使用。
Unsafe類的CAS操做多是用的最多的,它爲Java的鎖機制提供了一種新的解決辦法,好比AtomicInteger等類都是經過該方法來實現的。compareAndSwap方法是原子的,能夠避免繁重的鎖機制,提升代碼效率。這是一種樂觀鎖,一般認爲在大部分狀況下不出現競態條件,若是操做失敗,會不斷重試直到成功。
6、掛起與恢復。
這部分包括了park、unpark等方法。
將一個線程進行掛起是經過park方法實現的,調用 park後,線程將一直阻塞直到超時或者中斷等條件出現。unpark能夠終止一個掛起的線程,使其恢復正常。整個併發框架中對線程的掛起操做被封裝在 LockSupport類中,LockSupport類中有各類版本pack方法,但最終都調用了Unsafe.park()方法。
7、內存屏障。
這部分包括了loadFence、storeFence、fullFence等方法。這是在Java 8新引入的,用於定義內存屏障,避免代碼重排序。
loadFence() 表示該方法以前的全部load操做在內存屏障以前完成。同理storeFence()表示該方法以前的全部store操做在內存屏障以前完成。fullFence()表示該方法以前的全部load、store操做在內存屏障以前完成。