unsafe類是啥?
java最初被設計為一種安全的受控環境。盡管如此,java hotspot還是包含了一個“后門”,提供了一些可以直接操控內存和線程的低層次操作。這個后門類——sun.misc.unsafe——被jdk廣泛用于自己的包中,如java.nio和java.util.concurrent。但是絲毫不建議在生產環境中使用這個后門。因為這個api十分不安全、不輕便、而且不穩定。這個不安全的類提供了一個觀察hotspot jvm內部結構并且可以對其進行修改。有時它可以被用來在不適用c++調試的情況下學習虛擬機內部結構,有時也可以被拿來做性能監控和開發工具。
引言
最近在看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
2
3
4
5
6
7
8
|
public static unsafe getunsafe() { class var0 = reflection.getcallerclass(); if (!vm.issystemdomainloader(var0.getclassloader())) { throw new securityexception( "unsafe" ); } else { return theunsafe; } } |
網上也有一些辦法來用主類加載器加載用戶代碼,比如設置bootclasspath參數。但更簡單方法是利用java反射,方法如下:
1
2
3
|
field f = unsafe. class .getdeclaredfield( "theunsafe" ); f.setaccessible( true ); 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操作在內存屏障之前完成。
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對服務器之家的支持。
原文鏈接:http://www.cnblogs.com/pkufork/p/java_unsafe.html