簡單談一談Java中的Unsafe類
Unsafe類是啥?
Java最初被設計為一種安全的受控環(huán)境。盡管如此,Java HotSpot還是包含了一個“后門”,提供了一些可以直接操控內存和線程的低層次操作。這個后門類——sun.misc.Unsafe——被JDK廣泛用于自己的包中,如java.nio和java.util.concurrent。但是絲毫不建議在生產環(huán)境中使用這個后門。因為這個API十分不安全、不輕便、而且不穩(wěn)定。這個不安全的類提供了一個觀察HotSpot JVM內部結構并且可以對其進行修改。有時它可以被用來在不適用C++調試的情況下學習虛擬機內部結構,有時也可以被拿來做性能監(jiān)控和開發(fā)工具。
引言
最近在看Java并發(fā)包的源碼,發(fā)現(xiàn)了神奇的Unsafe類,仔細研究了一下,在這里跟大家分享一下。
Unsafe類是在sun.misc包下,不屬于Java標準。但是很多Java的基礎類庫,包括一些被廣泛使用的高性能開發(fā)庫都是基于Unsafe類開發(fā)的,比如Netty、Cassandra、Hadoop、Kafka等。Unsafe類在提升Java運行效率,增強Java語言底層操作能力方面起了很大的作用。
Unsafe類使Java擁有了像C語言的指針一樣操作內存空間的能力,同時也帶來了指針的問題。過度的使用Unsafe類會使得出錯的幾率變大,因此Java官方并不建議使用的,官方文檔也幾乎沒有。Oracle正在計劃從Java 9中去掉Unsafe類,如果真是如此影響就太大了。
通常我們最好也不要使用Unsafe類,除非有明確的目的,并且也要對它有深入的了解才行。要想使用Unsafe類需要用一些比較tricky的辦法。Unsafe類使用了單例模式,需要通過一個靜態(tài)方法getUnsafe()來獲取。但Unsafe類做了限制,如果是普通的調用的話,它會拋出一個SecurityException異常;只有由主類加載器加載的類才能調用這個方法。其源碼如下:
public static Unsafe getUnsafe() { Class var0 = Reflection.getCallerClass(); if(!VM.isSystemDomainLoader(var0.getClassLoader())) { throw new SecurityException("Unsafe"); } else { return theUnsafe; } }
網上也有一些辦法來用主類加載器加載用戶代碼,比如設置bootclasspath參數(shù)。但更簡單方法是利用Java反射,方法如下:
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(獲取內存地址指向的整數(shù))、getIntVolatile(獲取內存地址指向的整數(shù),并支持volatile語義)、putInt(將整數(shù)寫入指定內存地址)、putIntVolatile(將整數(shù)寫入指定內存地址,并支持volatile語義)、putOrderedInt(將整數(shù)寫入指定內存地址、有序或者有延遲的方法)等方法。getXXX和putXXX包含了各種基本類型的操作。
利用copyMemory方法,我們可以實現(xiàn)一個通用的對象拷貝方法,無需再對每一個對象都實現(xiàn)clone方法,當然這通用的方法只能做到對象淺拷貝。
二、非常規(guī)的對象實例化。
allocateInstance()方法提供了另一種創(chuàng)建實例的途徑。通常我們可以用new或者反射來實例化對象,使用allocateInstance()方法可以直接生成對象實例,且無需調用構造方法和其它初始化方法。
這在對象反序列化的時候會很有用,能夠重建和設置final字段,而不需要調用構造方法。
三、操作類、對象、變量。
這部分包括了staticFieldOffset(靜態(tài)域偏移)、defineClass(定義類)、defineAnonymousClass(定義匿名類)、ensureClassInitialized(確保類初始化)、objectFieldOffset(對象域偏移)等方法。
通過這些方法我們可以獲取對象的指針,通過對指針進行偏移,我們不僅可以直接修改指針指向的數(shù)據(即使它們是私有的),甚至可以找到JVM已經認定為垃圾、可以進行回收的對象。
四、數(shù)組操作。
這部分包括了arrayBaseOffset(獲取數(shù)組第一個元素的偏移地址)、arrayIndexScale(獲取數(shù)組中元素的增量地址)等方法。arrayBaseOffset與arrayIndexScale配合起來使用,就可以定位數(shù)組中每個元素在內存中的位置。
由于Java的數(shù)組最大值為Integer.MAX_VALUE,使用Unsafe類的內存分配方法可以實現(xiàn)超大數(shù)組。實際上這樣的數(shù)據就可以認為是C數(shù)組,因此需要注意在合適的時間釋放內存。
五、多線程同步。包括鎖機制、CAS操作等。
這部分包括了monitorEnter、tryMonitorEnter、monitorExit、compareAndSwapInt、compareAndSwap等方法。
其中monitorEnter、tryMonitorEnter、monitorExit已經被標記為deprecated,不建議使用。
Unsafe類的CAS操作可能是用的最多的,它為Java的鎖機制提供了一種新的解決辦法,比如AtomicInteger等類都是通過該方法來實現(xiàn)的。compareAndSwap方法是原子的,可以避免繁重的鎖機制,提高代碼效率。這是一種樂觀鎖,通常認為在大部分情況下不出現(xiàn)競態(tài)條件,如果操作失敗,會不斷重試直到成功。
六、掛起與恢復。
這部分包括了park、unpark等方法。
將一個線程進行掛起是通過park方法實現(xiàn)的,調用 park后,線程將一直阻塞直到超時或者中斷等條件出現(xiàn)。unpark可以終止一個掛起的線程,使其恢復正常。整個并發(fā)框架中對線程的掛起操作被封裝在 LockSupport類中,LockSupport類中有各種版本pack方法,但最終都調用了Unsafe.park()方法。
七、內存屏障。
這部分包括了loadFence、storeFence、fullFence等方法。這是在Java 8新引入的,用于定義內存屏障,避免代碼重排序。
loadFence() 表示該方法之前的所有l(wèi)oad操作在內存屏障之前完成。同理storeFence()表示該方法之前的所有store操作在內存屏障之前完成。fullFence()表示該方法之前的所有l(wèi)oad、store操作在內存屏障之前完成。
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
相關文章
實戰(zhàn)分布式醫(yī)療掛號系統(tǒng)之設置微服務搭建醫(yī)院模塊
這篇文章主要為大家介紹了實戰(zhàn)分布式醫(yī)療掛號系統(tǒng)之搭建醫(yī)院設置微服務模塊,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-04-04SpringBoot ResponseEntity標識Http響應方式
這篇文章主要介紹了SpringBoot ResponseEntity標識Http響應方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-07-07Java使用Graphics2D實現(xiàn)字符串文本自動換行
這篇文章主要為大家詳細介紹了Java如何使用Graphics2D實現(xiàn)字符串文本自動換行,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下2024-04-04