您好,登錄后才能下訂單哦!
這篇文章主要介紹了java中Unsafe類怎么用,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
Unsafe類使Java擁有了像C語言的指針一樣操作內存空間的能力,同時也帶來了指針的問題。過度的使用Unsafe類會使得出錯的幾率變大,因此Java官方并不建議使用的,官方文檔也幾乎沒有。Oracle正在計劃從Java 9中去掉Unsafe類,如果真是如此影響就太大了。
首先在Oracle的Jdk8無法獲取到sun.misc包的源碼,想看此包的源碼可以直接下載openjdk。
1、預備工作
openjdk的源碼我下載的是openjdk-8u40-src-b25-10_feb_2015,有需要的可以私信我,如果是我公眾號粉絲,我會直接附加上這個百度云資源。在下載完成之后,然后就可以直接導入我們的eclipse了。
windows->preference->installed jres->選中jre->edit->rt.jar->source attachment->external folders->openjdk源碼路徑。此時就可以查看我們的Unsafe類的源碼了。
2、簡介說明
如果你學習了一些java并發包里面的類源碼的話,對這個Unsafe類一定不陌生,整個java并發包底層實現的核心就是它,在很久之前盛傳著這個類將要在jdk9移除,事實上如果移除了那么一大批框架將會消失,比如說赫赫有名的Netty框架。最終jdk9出現的時候也只是對其進行了改進和優化。不過這也再一次說明了這個類的重要地位。
為什么說它一半是天使一半是魔鬼呢?要回答這個問題,我們還是要從其特性來解釋。
(1)Unsafe類使Java擁有了像C語言的指針一樣操作內存空間的能力,一旦能夠直接操作內存,這也就意味著(1)不受jvm管理,也就意味著無法被GC,需要我們手動GC,稍有不慎就會出現內存泄漏。
(2)Unsafe的不少方法中必須提供原始地址(內存地址)和被替換對象的地址,偏移量要自己計算,一旦出現問題就是JVM崩潰級別的異常,會導致整個JVM實例崩潰,表現為應用程序直接crash掉。
(3)直接操作內存,也意味著其速度更快,在高并發的條件之下能夠很好地提高效率。
因此,從上面三個角度來看,雖然在一定程度上提升了效率但是也帶來了指針的不安全性。
下面我們深入到源碼中看看,提供了什么方法直接操作內存。
Unsafe中一共有82個public native修飾的方法,還有幾十個基于這82個public native方法的其他方法。這些方法大體可以歸結為以下幾類:
(1)初始化操作
(2)操作對象屬性
(3)操作數組元素
(4)內存管理
(5)內存屏障
(6)線程掛起和回復
(7)CAS機制
下面我們對這些方法盡進行一個大致的分析。
1、初始化
//1、注冊native方法,是的Unsafe類可以操作C語言 private static native void registerNatives(); static { registerNatives(); sun.reflect.Reflection.registerMethodsToFilter(Unsafe.class, "getUnsafe"); } //2、構造方法 private Unsafe() {} //3、初始化方法 private static final Unsafe theUnsafe = new Unsafe(); //4、初始化方法實現 @CallerSensitive public static Unsafe getUnsafe() { Class> caller = Reflection.getCallerClass(); if (!VM.isSystemDomainLoader(caller.getClassLoader())) throw new SecurityException("Unsafe"); return theUnsafe; }
在這里我們看到Unsafe的初始化方法主要是通過getUnsafe方法的單例模式實現的,調用JVM本地方法registerNatives()和sun.reflect.Reflection,通過Reflection的getCallerClass判斷當前調用的類是否是主類加載器(BootStrap classLoader)加載的,否則的話拋出一個SecurityException。這也證明了一個問題,那就是只有由主類加載器(BootStrap classLoader)加載的類才能調用這個類中的方法。
2、操作屬性方法
(1)public native Object getObject(Object o, long offset);
通過給定的Java變量獲取引用值。這里實際上是獲取一個Java對象o中,獲取偏移地址為offset的屬性的值,此方法可以突破修飾符的抑制,也就是無視private、protected和default修飾符。類似的方法有getInt、getDouble等等。同理還有putObject方法。
(2)public native Object getObjectVolatile(Object o, long offset);
強制從主存中獲取屬性值。類似的方法有getIntVolatile、getDoubleVolatile等等。同理還有putObjectVolatile。
(3)public native void putOrderedObject(Object o, long offset, Object x);
設置o對象中offset偏移地址offset對應的Object型field的值為指定值x。這是一個有序或者有延遲的putObjectVolatile方法,并且不保證值的改變被其他線程立即看到。只有在field被volatile修飾并且期望被修改的時候使用才會生效。類似的方法有putOrderedInt和putOrderedLong。
(4)public native long staticFieldOffset(Field f);
返回給定的靜態屬性在它的類的存儲分配中的位置(偏移地址)。
(5)public native long objectFieldOffset(Field f);
返回給定的非靜態屬性在它的類的存儲分配中的位置(偏移地址)。
(6)public native Object staticFieldBase(Field f);
返回給定的靜態屬性的位置,配合staticFieldOffset方法使用。
3、操作數組
(1)public native int arrayBaseOffset(Class arrayClass);
返回數組類型的第一個元素的偏移地址(基礎偏移地址)。
(2)public native int arrayIndexScale(Class arrayClass);
返回數組中元素與元素之間的偏移地址的增量。
這兩個方法配合使用就可以定位到任何一個元素的地址。
4、內存管理
(1)public native int addressSize();
獲取本地指針的大小(單位是byte),通常值為4或者8。常量ADDRESS_SIZE就是調用此方法。
(2)public native int pageSize();
獲取本地內存的頁數,此值為2的冪次方。
(3)public native long allocateMemory(long bytes);
分配一塊新的本地內存,通過bytes指定內存塊的大小(單位是byte),返回新開辟的內存的地址。
(4)public native long reallocateMemory(long address, long bytes);
通過指定的內存地址address重新調整本地內存塊的大小,調整后的內存塊大小通過bytes指定(單位為byte)。
(5)public native void setMemory(Object o, long offset, long bytes, byte value);
將給定內存塊中的所有字節設置為固定值(通常是0)。
5、線程掛起和恢復
(1)public native void unpark(Object thread);
釋放被park創建的在一個線程上的阻塞。由于其不安全性,因此必須保證線程是存活的。
(2)public native void park(boolean isAbsolute, long time);
阻塞當前線程,一直等道unpark方法被調用。
6、內存屏障
(1)public native void loadFence();
在該方法之前的所有讀操作,一定在load屏障之前執行完成。
(2)public native void storeFence();
在該方法之前的所有寫操作,一定在store屏障之前執行完成
(3)public native void fullFence();
在該方法之前的所有讀寫操作,一定在full屏障之前執行完成,這個內存屏障相當于上面兩個(load屏障和store屏障)的合體功能。
7、CAS機制
public final native boolean compareAndSwapObject( Object o, long offset, Object expected, Object x); public final native boolean compareAndSwapInt( Object o, long offset,int expected, int x); public final native boolean compareAndSwapLong( Object o, long offset, long expected,long x);
這個Unsafe類其實是貫穿到整個java并發包體系中的,不管是你看原子包還是lock包底部都有這樣的一個類,我們需要記住的不是每一個方法,而是上面七類的標題。也就是具體有什么功能。
說了這么久的源碼在這里才介紹其使用,是因為官方并不推薦我們使用,也就是說我們無法直接new出來一個Unsafe類出來,那我們該如何使用呢?在很久之前我曾寫過一篇介紹java反射機制的文章,沒錯就是這個反射機制,牛的不行。Unsafe就可以通過反射機制來獲取。
public class UnsafeTest { public static void main(String[] args) throws Exception { //這里的theUnsafe就是我們源碼中的那個theUnsafe Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); theUnsafe.setAccessible(true); Unsafe unsafe = (Unsafe) theUnsafe.get(null); //1、創建對象實例 Author author = (Author) unsafe.allocateInstance(Author.class); //2、操作對象的屬性 Field ageField = Author.class.getDeclaredField("age"); long fieldOffset = unsafe.objectFieldOffset(ageField); //3、操作數組 String[] strings = new String[]{"1", "2", "3"}; long i = unsafe.arrayBaseOffset(String[].class); //4、操作內存 long address = unsafe.allocateMemory(8L); } }
感謝你能夠認真閱讀完這篇文章,希望小編分享的“java中Unsafe類怎么用”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關注億速云行業資訊頻道,更多相關知識等著你來學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。