精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

Java雙刃劍之Unsafe類詳解

開發 后端
本文中,我們首先介紹Unsafe的基本概念、工作原理,并在此基礎上,對它的API進行了說明與實踐。相信大家通過這一過程,能夠發現Unsafe在某些場景下,確實能夠為我們提供編程中的便利。

[[396732]]

 前一段時間在研究juc源碼的時候,發現在很多工具類中都調用了一個Unsafe類中的方法,出于好奇就想要研究一下這個類到底有什么作用,于是先查閱了一些資料,一查不要緊,很多資料中對Unsafe的態度都是這樣的畫風:


其實看到這些說法也沒什么意外,畢竟Unsafe這個詞直譯過來就是“不安全的”,從名字里我們也大概能看來Java的開發者們對它有些不放心。但是作為一名極客,不能你說不安全我就不去研究了,畢竟只有了解一項技術的風險點,才能更好的避免出現這些問題嘛。

下面我們言歸正傳,先通過簡單的介紹來對Unsafe類有一個大致的了解。Unsafe類是一個位于sun.misc包下的類,它提供了一些相對底層方法,能夠讓我們接觸到一些更接近操作系統底層的資源,如系統的內存資源、cpu指令等。而通過這些方法,我們能夠完成一些普通方法無法實現的功能,例如直接使用偏移地址操作對象、數組等等。但是在使用這些方法提供的便利的同時,也存在一些潛在的安全因素,例如對內存的錯誤操作可能會引起內存泄漏,嚴重時甚至可能引起jvm崩潰。因此在使用Unsafe前,我們必須要了解它的工作原理與各方法的應用場景,并且在此基礎上仍需要非常謹慎的操作,下面我們正式開始對Unsafe的學習。

Unsafe 基礎

首先我們來嘗試獲取一個Unsafe實例,如果按照new的方式去創建對象,不好意思,編譯器會報錯提示你:

  1. Unsafe() has private access in 'sun.misc.Unsafe' 

查看Unsafe類的源碼,可以看到它被final修飾不允許被繼承,并且構造函數為private類型,即不允許我們手動調用構造方法進行實例化,只有在static靜態代碼塊中,以單例的方式初始化了一個Unsafe對象:

  1. public final class Unsafe { 
  2.     private static final Unsafe theUnsafe; 
  3.     ... 
  4.     private Unsafe() { 
  5.     } 
  6.     ... 
  7.     static { 
  8.         theUnsafe = new Unsafe(); 
  9.     }    

在Unsafe類中,提供了一個靜態方法getUnsafe,看上去貌似可以用它來獲取Unsafe實例:

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

但是如果我們直接調用這個靜態方法,會拋出異常:

  1. Exception in thread "main" java.lang.SecurityException: Unsafe 
  2.  at sun.misc.Unsafe.getUnsafe(Unsafe.java:90) 
  3.  at com.cn.test.GetUnsafeTest.main(GetUnsafeTest.java:12) 

這是因為在getUnsafe方法中,會對調用者的classLoader進行檢查,判斷當前類是否由Bootstrap classLoader加載,如果不是的話那么就會拋出一個SecurityException異常。也就是說,只有啟動類加載器加載的類才能夠調用Unsafe類中的方法,來防止這些方法在不可信的代碼中被調用。

那么,為什么要對Unsafe類進行這么謹慎的使用限制呢,說到底,還是因為它實現的功能過于底層,例如直接進行內存操作、繞過jvm的安全檢查創建對象等等,概括的來說,Unsafe類實現功能可以被分為下面8類:

創建實例

看到上面的這些功能,你是不是已經有些迫不及待想要試一試了。那么如果我們執意想要在自己的代碼中調用Unsafe類的方法,應該怎么獲取一個它的實例對象呢,答案是利用反射獲得Unsafe類中已經實例化完成的單例對象:

  1. public static Unsafe getUnsafe() throws IllegalAccessException { 
  2.     Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe"); 
  3.     //Field unsafeField = Unsafe.class.getDeclaredFields()[0]; //也可以這樣,作用相同 
  4.     unsafeField.setAccessible(true); 
  5.     Unsafe unsafe =(Unsafe) unsafeField.get(null); 
  6.     return unsafe; 

在獲取到Unsafe的實例對象后,我們就可以使用它為所欲為了,先來嘗試使用它對一個對象的屬性進行讀寫:

  1. public void fieldTest(Unsafe unsafe) throws NoSuchFieldException { 
  2.     User user=new User(); 
  3.     long fieldOffset = unsafe.objectFieldOffset(User.class.getDeclaredField("age")); 
  4.     System.out.println("offset:"+fieldOffset); 
  5.     unsafe.putInt(user,fieldOffset,20); 
  6.     System.out.println("age:"+unsafe.getInt(user,fieldOffset)); 
  7.     System.out.println("age:"+user.getAge()); 

運行代碼輸出如下,可以看到通過Unsafe類的objectFieldOffset方法獲取了對象中字段的偏移地址,這個偏移地址不是內存中的絕對地址而是一個相對地址,之后再通過這個偏移地址對int類型字段的屬性值進行了讀寫操作,通過結果也可以看到Unsafe的方法和類中的get方法獲取到的值是相同的。

  1. offset:12 
  2. age:20 
  3. age:20 

在上面的例子中調用了Unsafe類的putInt和getInt方法,看一下源碼中的方法:

  1. public native int getInt(Object o, long offset); 
  2. public native void putInt(Object o, long offset, int x); 

先說作用,getInt用于從對象的指定偏移地址處讀取一個int,putInt用于在對象指定偏移地址處寫入一個int,并且即使類中的這個屬性是private私有類型的,也可以對它進行讀寫。但是有細心的小伙伴可能發現了,這兩個方法相對于我們平常寫的普通方法,多了一個native關鍵字修飾,并且沒有具體的方法邏輯,那么它是怎么實現的呢?

native方法

在java中,這類方法被稱為native方法(Native Method),簡單的說就是由java調用非java代碼的接口,被調用的方法是由非java 語言實現的,例如它可以由C或C++語言來實現,并編譯成DLL,然后直接供java進行調用。native方法是通過JNI(Java Native Interface)實現調用的,從 java1.1開始 JNI 標準就是java平臺的一部分,它允許java代碼和其他語言的代碼進行交互。

Unsafe類中的很多基礎方法都屬于native方法,那么為什么要使用native方法呢?原因可以概括為以下幾點:

  • 需要用到 java 中不具備的依賴于操作系統的特性,java在實現跨平臺的同時要實現對底層的控制,需要借助其他語言發揮作用
  • 對于其他語言已經完成的一些現成功能,可以使用java直接調用
  • 程序對時間敏感或對性能要求非常高時,有必要使用更加底層的語言,例如C/C++甚至是匯編

在juc包的很多并發工具類在實現并發機制時,都調用了native方法,通過它們打破了java運行時的界限,能夠接觸到操作系統底層的某些功能。對于同一個native方法,不同的操作系統可能會通過不同的方式來實現,但是對于使用者來說是透明的,最終都會得到相同的結果,至于java如何實現的通過JNI調用其他語言的代碼,不是本文的重點,會在后續的文章中具體學習。

Unsafe 應用

在對Unsafe的基礎有了一定了解后,我們來看一下它的基本應用。由于篇幅有限,不能對所有方法進行介紹,如果大家有學習的需要,可以下載openJDK的源碼進行學習。

1、內存操作

如果你是一個寫過c或者c++的程序員,一定對內存操作不會陌生,而在java中是不允許直接對內存進行操作的,對象內存的分配和回收都是由jvm自己實現的。但是在Unsafe中,提供的下列接口可以直接進行內存操作:

  1. //分配新的本地空間 
  2. public native long allocateMemory(long bytes); 
  3. //重新調整內存空間的大小 
  4. public native long reallocateMemory(long address, long bytes); 
  5. //將內存設置為指定值 
  6. public native void setMemory(Object o, long offset, long bytes, byte value); 
  7. //內存拷貝 
  8. public native void copyMemory(Object srcBase, long srcOffset,Object destBase, long destOffset,long bytes); 
  9. //清除內存 
  10. public native void freeMemory(long address); 

使用下面的代碼進行測試:

  1. private void memoryTest() { 
  2.     int size = 4; 
  3.     long addr = unsafe.allocateMemory(size); 
  4.     long addr3 = unsafe.reallocateMemory(addr, size * 2); 
  5.     System.out.println("addr: "+addr); 
  6.     System.out.println("addr3: "+addr3); 
  7.     try { 
  8.         unsafe.setMemory(null,addr ,size,(byte)1); 
  9.         for (int i = 0; i < 2; i++) { 
  10.             unsafe.copyMemory(null,addr,null,addr3+size*i,4); 
  11.         } 
  12.         System.out.println(unsafe.getInt(addr)); 
  13.         System.out.println(unsafe.getLong(addr3)); 
  14.     }finally { 
  15.         unsafe.freeMemory(addr); 
  16.         unsafe.freeMemory(addr3); 
  17.     } 

先看結果輸出:

  1. addr: 2433733895744 
  2. addr3: 2433733894944 
  3. 16843009 
  4. 72340172838076673 

分析一下運行結果,首先使用allocateMemory方法申請4字節長度的內存空間,在循環中調用setMemory方法向每個字節寫入內容為byte類型的1,當使用Unsafe調用getInt方法時,因為一個int型變量占4個字節,會一次性讀取4個字節,組成一個int的值,對應的十進制結果為16843009,可以通過圖示理解這個過程:

在代碼中調用reallocateMemory方法重新分配了一塊8字節長度的內存空間,通過比較addr和addr3可以看到和之前申請的內存地址是不同的。在代碼中的第二個for循環里,調用copyMemory方法進行了兩次內存的拷貝,每次拷貝內存地址addr開始的4個字節,分別拷貝到以addr3和addr3+4開始的內存空間上:

拷貝完成后,使用getLong方法一次性讀取8個字節,得到long類型的值為72340172838076673。

需要注意,通過這種方式分配的內存屬于堆外內存,是無法進行垃圾回收的,需要我們把這些內存當做一種資源去手動調用freeMemory方法進行釋放,否則會產生內存泄漏。通用的操作內存方式是在try中執行對內存的操作,最終在finally塊中進行內存的釋放。

2、內存屏障

在介紹內存屏障前,需要知道編譯器和CPU會在保證程序輸出結果一致的情況下,會對代碼進行重排序,從指令優化角度提升性能。而指令重排序可能會帶來一個不好的結果,導致CPU的高速緩存和內存中數據的不一致,而內存屏障(Memory Barrier)就是通過組織屏障兩邊的指令重排序從而避免編譯器和硬件的不正確優化情況。

在硬件層面上,內存屏障是CPU為了防止代碼進行重排序而提供的指令,不同的硬件平臺上實現內存屏障的方法可能并不相同。在java8中,引入了3個內存屏障的函數,它屏蔽了操作系統底層的差異,允許在代碼中定義、并統一由jvm來生成內存屏障指令,來實現內存屏障的功能。Unsafe中提供了下面三個內存屏障相關方法:

  1. //禁止讀操作重排序 
  2. public native void loadFence(); 
  3. //禁止寫操作重排序 
  4. public native void storeFence(); 
  5. //禁止讀、寫操作重排序 
  6. public native void fullFence(); 

內存屏障可以看做對內存隨機訪問的操作中的一個同步點,使得此點之前的所有讀寫操作都執行后才可以開始執行此點之后的操作。以loadFence方法為例,它會禁止讀操作重排序,保證在這個屏障之前的所有讀操作都已經完成,并且將緩存數據設為無效,重新從主存中進行加載。

看到這估計很多小伙伴們會想到volatile關鍵字了,如果在字段上添加了volatile關鍵字,就能夠實現字段在多線程下的可見性。基于讀內存屏障,我們也能實現相同的功能。下面定義一個線程方法,在線程中去修改flag標志位,注意這里的flag是沒有被volatile修飾的:

  1. @Getter 
  2. class ChangeThread implements Runnable{ 
  3.     /**volatile**/ boolean flag=false
  4.     @Override 
  5.     public void run() { 
  6.         try { 
  7.             Thread.sleep(3000); 
  8.         } catch (InterruptedException e) { 
  9.             e.printStackTrace(); 
  10.         }         
  11.         System.out.println("subThread change flag to:" + flag); 
  12.         flag = true
  13.     } 

在主線程的while循環中,加入內存屏障,測試是否能夠感知到flag的修改變化:

  1. public static void main(String[] args){ 
  2.     ChangeThread changeThread = new ChangeThread(); 
  3.     new Thread(changeThread).start(); 
  4.     while (true) { 
  5.         boolean flag = changeThread.isFlag(); 
  6.         unsafe.loadFence(); //加入讀內存屏障 
  7.         if (flag){ 
  8.             System.out.println("detected flag changed"); 
  9.             break; 
  10.         } 
  11.     } 
  12.     System.out.println("main thread end"); 

運行結果:

  1. subThread change flag to:false 
  2. detected flag changed 
  3. main thread end 

而如果刪掉上面代碼中的loadFence方法,那么主線程將無法感知到flag發生的變化,會一直在while中循環。可以用圖來表示上面的過程:

了解java內存模型(JMM)的小伙伴們應該清楚,運行中的線程不是直接讀取主內存中的變量的,只能操作自己工作內存中的變量,然后同步到主內存中,并且線程的工作內存是不能共享的。上面的圖中的流程就是子線程借助于主內存,將修改后的結果同步給了主線程,進而修改主線程中的工作空間,跳出循環。

3、對象操作

a、對象成員屬性的內存偏移量獲取,以及字段屬性值的修改,在上面的例子中我們已經測試過了。除了前面的putInt、getInt方法外,Unsafe提供了全部8種基礎數據類型以及Object的put和get方法,并且所有的put方法都可以越過訪問權限,直接修改內存中的數據。閱讀openJDK源碼中的注釋發現,基礎數據類型和Object的讀寫稍有不同,基礎數據類型是直接操作的屬性值(value),而Object的操作則是基于引用值(reference value)。下面是Object的讀寫方法:

  1. //在對象的指定偏移地址獲取一個對象引用 
  2. public native Object getObject(Object o, long offset); 
  3. //在對象指定偏移地址寫入一個對象引用 
  4. public native void putObject(Object o, long offset, Object x); 

除了對象屬性的普通讀寫外,Unsafe還提供了volatile讀寫和有序寫入方法。volatile讀寫方法的覆蓋范圍與普通讀寫相同,包含了全部基礎數據類型和Object類型,以int類型為例:

  1. //在對象的指定偏移地址處讀取一個int值,支持volatile load語義 
  2. public native int getIntVolatile(Object o, long offset); 
  3. //在對象指定偏移地址處寫入一個int,支持volatile store語義 
  4. public native void putIntVolatile(Object o, long offset, int x); 

相對于普通讀寫來說,volatile讀寫具有更高的成本,因為它需要保證可見性和有序性。在執行get操作時,會強制從主存中獲取屬性值,在使用put方法設置屬性值時,會強制將值更新到主存中,從而保證這些變更對其他線程是可見的。

有序寫入的方法有以下三個:

  1. public native void putOrderedObject(Object o, long offset, Object x); 
  2. public native void putOrderedInt(Object o, long offset, int x); 
  3. public native void putOrderedLong(Object o, long offset, long x); 

有序寫入的成本相對volatile較低,因為它只保證寫入時的有序性,而不保證可見性,也就是一個線程寫入的值不能保證其他線程立即可見。為了解決這里的差異性,需要對內存屏障的知識點再進一步進行補充,首先需要了解兩個指令的概念:

  • Load:將主內存中的數據拷貝到處理器的緩存中
  • Store:將處理器緩存的數據刷新到主內存中

順序寫入與volatile寫入的差別在于,在順序寫時加入的內存屏障類型為StoreStore類型,而在volatile寫入時加入的內存屏障是StoreLoad類型,如下圖所示:

在有序寫入方法中,使用的是StoreStore屏障,該屏障確保Store1立刻刷新數據到內存,這一操作先于Store2以及后續的存儲指令操作。而在volatile寫入中,使用的是StoreLoad屏障,該屏障確保Store1立刻刷新數據到內存,這一操作先于Load2及后續的裝載指令,并且,StoreLoad屏障會使該屏障之前的所有內存訪問指令,包括存儲指令和訪問指令全部完成之后,才執行該屏障之后的內存訪問指令。

綜上所述,在上面的三類寫入方法中,在寫入效率方面,按照put、putOrder、putVolatile的順序效率逐漸降低,

b、使用Unsafe的allocateInstance方法,允許我們使用非常規的方式進行對象的實例化,首先定義一個實體類,并且在構造函數中對其成員變量進行賦值操作:

  1. @Data 
  2. public class A { 
  3.     private int b; 
  4.     public A(){ 
  5.         this.b =1; 
  6.     } 

分別基于構造函數、反射以及Unsafe方法的不同方式創建對象進行比較:

  1. public void objTest() throws Exception{ 
  2.     A a1=new A(); 
  3.     System.out.println(a1.getB()); 
  4.     A a2 = A.class.newInstance(); 
  5.     System.out.println(a2.getB()); 
  6.     A a3= (A) unsafe.allocateInstance(A.class); 
  7.     System.out.println(a3.getB()); 

打印結果分別為1、1、0,說明通過allocateInstance方法創建對象過程中,不會調用類的構造方法。使用這種方式創建對象時,只用到了Class對象,所以說如果想要跳過對象的初始化階段或者跳過構造器的安全檢查,就可以使用這種方法。在上面的例子中,如果將A類的構造函數改為private類型,將無法通過構造函數和反射創建對象,但allocateInstance方法仍然有效。

4、數組操作

在Unsafe中,可以使用arrayBaseOffset方法可以獲取數組中第一個元素的偏移地址,使用arrayIndexScale方法可以獲取數組中元素間的偏移地址增量。使用下面的代碼進行測試:

  1. private void arrayTest() { 
  2.     String[] array=new String[]{"str1str1str","str2","str3"}; 
  3.     int baseOffset = unsafe.arrayBaseOffset(String[].class); 
  4.     System.out.println(baseOffset); 
  5.     int scale = unsafe.arrayIndexScale(String[].class); 
  6.     System.out.println(scale); 
  7.  
  8.     for (int i = 0; i < array.length; i++) { 
  9.         int offset=baseOffset+scale*i; 
  10.         System.out.println(offset+" : "+unsafe.getObject(array,offset)); 
  11.     } 

上面代碼的輸出結果為:

  1. 16 
  2. 16 : str1str1str 
  3. 20 : str2 
  4. 24 : str3 

通過配合使用數組偏移首地址和各元素間偏移地址的增量,可以方便的定位到數組中的元素在內存中的位置,進而通過getObject方法直接獲取任意位置的數組元素。需要說明的是,arrayIndexScale獲取的并不是數組中元素占用的大小,而是地址的增量,按照openJDK中的注釋,可以將它翻譯為元素尋址的轉換因子(scale factor for addressing elements)。在上面的例子中,第一個字符串長度為11字節,但其地址增量仍然為4字節。

那么,基于這兩個值是如何實現的尋址和數組元素的訪問呢,這里需要借助一點在前面的文章中講過的Java對象內存布局的知識,先把上面例子中的String數組對象的內存布局畫出來,就很方便大家理解了:

在String數組對象中,對象頭包含3部分,mark word標記字占用8字節,klass point類型指針占用4字節,數組對象特有的數組長度部分占用4字節,總共占用了16字節。第一個String的引用類型相對于對象的首地址的偏移量是就16,之后每個元素在這個基礎上加4,正好對應了我們上面代碼中的尋址過程,之后再使用前面說過的getObject方法,通過數組對象可以獲得對象在堆中的首地址,再配合對象中變量的偏移量,就能獲得每一個變量的引用。

5、CAS操作

在juc包的并發工具類中大量地使用了CAS操作,像在前面介紹synchronized和AQS的文章中也多次提到了CAS,其作為樂觀鎖在并發工具類中廣泛發揮了作用。在Unsafe類中,提供了compareAndSwapObject、compareAndSwapInt、compareAndSwapLong方法來實現的對Object、int、long類型的CAS操作。以compareAndSwapInt方法為例:

  1. public final native boolean compareAndSwapInt(Object o, long offset,int expected,int x); 

參數中o為需要更新的對象,offset是對象o中整形字段的偏移量,如果這個字段的值與expected相同,則將字段的值設為x這個新值,并且此更新是不可被中斷的,也就是一個原子操作。下面是一個使用compareAndSwapInt的例子:

  1. private volatile int a; 
  2. public static void main(String[] args){ 
  3.     CasTest casTest=new CasTest(); 
  4.     new Thread(()->{ 
  5.         for (int i = 1; i < 5; i++) { 
  6.             casTest.increment(i); 
  7.             System.out.print(casTest.a+" "); 
  8.         } 
  9.     }).start(); 
  10.     new Thread(()->{ 
  11.         for (int i = 5 ; i <10 ; i++) { 
  12.             casTest.increment(i); 
  13.             System.out.print(casTest.a+" "); 
  14.         } 
  15.     }).start(); 
  16.  
  17. private void increment(int x){ 
  18.     while (true){ 
  19.         try { 
  20.             long fieldOffset = unsafe.objectFieldOffset(CasTest.class.getDeclaredField("a")); 
  21.             if (unsafe.compareAndSwapInt(this,fieldOffset,x-1,x)) 
  22.                 break; 
  23.         } catch (NoSuchFieldException e) { 
  24.             e.printStackTrace(); 
  25.         } 
  26.     } 

運行代碼會依次輸出:

  1. 1 2 3 4 5 6 7 8 9  

在上面的例子中,使用兩個線程去修改int型屬性a的值,并且只有在a的值等于傳入的參數x減一時,才會將a的值變為x,也就是實現對a的加一的操作。流程如下所示:

需要注意的是,在調用compareAndSwapInt方法后,會直接返回true或false的修改結果,因此需要我們在代碼中手動添加自旋的邏輯。在AtomicInteger類的設計中,也是采用了將compareAndSwapInt的結果作為循環條件,直至修改成功才退出死循環的方式來實現的原子性的自增操作。

6、線程調度

Unsafe類中提供了park、unpark、monitorEnter、monitorExit、tryMonitorEnter方法進行線程調度,在前面介紹AQS的文章中我們提到過使用LockSupport掛起或喚醒指定線程,看一下LockSupport的源碼,可以看到它也是調用的Unsafe類中的方法:

  1. public static void park(Object blocker) { 
  2.     Thread t = Thread.currentThread(); 
  3.     setBlocker(t, blocker); 
  4.     UNSAFE.park(false, 0L); 
  5.     setBlocker(t, null); 
  6. public static void unpark(Thread thread) { 
  7.     if (thread != null
  8.         UNSAFE.unpark(thread); 

LockSupport的park方法調用了Unsafe的park方法來阻塞當前線程,此方法將線程阻塞后就不會繼續往后執行,直到有其他線程調用unpark方法喚醒當前線程。下面的例子對Unsafe的這兩個方法進行測試:

  1. public static void main(String[] args) { 
  2.     Thread mainThread = Thread.currentThread(); 
  3.     new Thread(()->{ 
  4.         try { 
  5.             TimeUnit.SECONDS.sleep(5); 
  6.             System.out.println("subThread try to unpark mainThread"); 
  7.             unsafe.unpark(mainThread); 
  8.         } catch (InterruptedException e) { 
  9.             e.printStackTrace(); 
  10.         } 
  11.     }).start(); 
  12.  
  13.     System.out.println("park main mainThread"); 
  14.     unsafe.park(false,0L); 
  15.     System.out.println("unpark mainThread success"); 

程序輸出為:

  1. park main mainThread 
  2. subThread try to unpark mainThread 
  3. unpark mainThread success 

程序運行的流程也比較容易看懂,子線程開始運行后先進行睡眠,確保主線程能夠調用park方法阻塞自己,子線程在睡眠5秒后,調用unpark方法喚醒主線程,使主線程能繼續向下執行。整個流程如下圖所示:

此外,Unsafe源碼中monitor相關的三個方法已經被標記為deprecated,不建議被使用:

  1. //獲得對象鎖 
  2. @Deprecated 
  3. public native void monitorEnter(Object var1); 
  4. //釋放對象鎖 
  5. @Deprecated 
  6. public native void monitorExit(Object var1); 
  7. //嘗試獲得對象鎖 
  8. @Deprecated 
  9. public native boolean tryMonitorEnter(Object var1); 

monitorEnter方法用于獲得對象鎖,monitorExit用于釋放對象鎖,如果對一個沒有被monitorEnter加鎖的對象執行此方法,會拋出IllegalMonitorStateException異常。tryMonitorEnter方法嘗試獲取對象鎖,如果成功則返回true,反之返回false。

7、Class操作

Unsafe對Class的相關操作主要包括類加載和靜態變量的操作方法。

a、靜態屬性讀取相關的方法:

  1. //獲取靜態屬性的偏移量 
  2. public native long staticFieldOffset(Field f); 
  3. //獲取靜態屬性的對象指針 
  4. public native Object staticFieldBase(Field f); 
  5. //判斷類是否需要實例化(用于獲取類的靜態屬性前進行檢測) 
  6. public native boolean shouldBeInitialized(Class<?> c); 

創建一個包含靜態屬性的類,進行測試:

  1. @Data 
  2. public class User { 
  3.     public static String name="Hydra"
  4.     int age; 
  5. private void staticTest() throws Exception { 
  6.     User user=new User(); 
  7.     System.out.println(unsafe.shouldBeInitialized(User.class)); 
  8.     Field sexField = User.class.getDeclaredField("name"); 
  9.     long fieldOffset = unsafe.staticFieldOffset(sexField); 
  10.     Object fieldBase = unsafe.staticFieldBase(sexField); 
  11.     Object object = unsafe.getObject(fieldBase, fieldOffset); 
  12.     System.out.println(object); 

運行結果:

  1. false 
  2. Hydra 

在Unsafe的對象操作中,我們學習了通過objectFieldOffset方法獲取對象屬性偏移量并基于它對變量的值進行存取,但是它不適用于類中的靜態屬性,這時候就需要使用staticFieldOffset方法。在上面的代碼中,只有在獲取Field對象的過程中依賴到了Class,而獲取靜態變量的屬性時不再依賴于Class。

在上面的代碼中首先創建一個User對象,這是因為如果一個類沒有被實例化,那么它的靜態屬性也不會被初始化,最后獲取的字段屬性將是null。所以在獲取靜態屬性前,需要調用shouldBeInitialized方法,判斷在獲取前是否需要初始化這個類。如果刪除創建User對象的語句,運行結果會變為:

  1. true 
  2. null 

b、使用defineClass方法允許程序在運行時動態地創建一個類,方法定義如下:

  1. public native Class<?> defineClass(String name, byte[] b, int offint len, 
  2.                                    ClassLoader loader,ProtectionDomain protectionDomain); 

在實際使用過程中,可以只傳入字節數組、起始字節的下標以及讀取的字節長度,默認情況下,類加載器(ClassLoader)和保護域(ProtectionDomain)來源于調用此方法的實例。下面的例子中實現了反編譯生成后的class文件的功能:

  1. private static void defineTest() { 
  2.     String fileName="F:\\workspace\\unsafe-test\\target\\classes\\com\\cn\\model\\User.class"
  3.     File file = new File(fileName); 
  4.     try(FileInputStream fis = new FileInputStream(file)) { 
  5.         byte[] content=new byte[(int)file.length()]; 
  6.         fis.read(content); 
  7.         Class clazz = unsafe.defineClass(null, content, 0, content.length, nullnull); 
  8.         Object o = clazz.newInstance(); 
  9.         Object age = clazz.getMethod("getAge").invoke(o, null); 
  10.         System.out.println(age); 
  11.     } catch (Exception e) { 
  12.         e.printStackTrace(); 
  13.     } 

在上面的代碼中,首先讀取了一個class文件并通過文件流將它轉化為字節數組,之后使用defineClass方法動態的創建了一個類,并在后續完成了它的實例化工作,流程如下圖所示,并且通過這種方式創建的類,會跳過JVM的所有安全檢查。

除了defineClass方法外,Unsafe還提供了一個defineAnonymousClass方法:

  1. public native Class<?> defineAnonymousClass(Class<?> hostClass, byte[] data, Object[] cpPatches); 

使用該方法可以用來動態的創建一個匿名類,在Lambda表達式中就是使用ASM動態生成字節碼,然后利用該方法定義實現相應的函數式接口的匿名類。在jdk15發布的新特性中,在隱藏類(Hidden classes)一條中,指出將在未來的版本中棄用Unsafe的defineAnonymousClass方法。

8、系統信息

Unsafe中提供的addressSize和pageSize方法用于獲取系統信息,調用addressSize方法會返回系統指針的大小,如果在64位系統下默認會返回8,而32位系統則會返回4。調用pageSize方法會返回內存頁的大小,值為2的整數冪。使用下面的代碼可以直接進行打印:

  1. private void systemTest() { 
  2.     System.out.println(unsafe.addressSize()); 
  3.     System.out.println(unsafe.pageSize()); 

執行結果:

  1. 4096 

這兩個方法的應用場景比較少,在java.nio.Bits類中,在使用pageCount計算所需的內存頁的數量時,調用了pageSize方法獲取內存頁的大小。另外,在使用copySwapMemory方法拷貝內存時,調用了addressSize方法,檢測32位系統的情況。

總結

在本文中,我們首先介紹了Unsafe的基本概念、工作原理,并在此基礎上,對它的API進行了說明與實踐。相信大家通過這一過程,能夠發現Unsafe在某些場景下,確實能夠為我們提供編程中的便利。但是回到開頭的話題,在使用這些便利時,確實存在著一些安全上的隱患,在我看來,一項技術具有不安全因素并不可怕,可怕的是它在使用過程中被濫用。盡管之前有傳言說會在java9中移除Unsafe類,不過它還是照樣已經存活到了jdk16,按照存在即合理的邏輯,只要使用得當,它還是能給我們帶來不少的幫助,因此最后還是建議大家,在使用Unsafe的過程中一定要做到使用謹慎使用、避免濫用。

 

責任編輯:姜華 來源: 碼農參上
相關推薦

2017-08-14 14:51:15

2019-12-18 15:30:57

漏洞安全Linux

2012-12-25 12:42:46

應用審查App Store

2009-03-05 10:50:00

空中上網

2012-03-05 16:37:55

2015-05-27 16:35:59

2010-09-02 14:52:20

CSS框架

2014-02-11 08:57:50

云計算IT架構IT運營

2019-03-21 14:12:27

數據管理物聯網物聯網安全

2025-05-27 10:10:00

Java緩存開發

2025-04-07 08:30:00

緩存Java開發

2014-05-06 09:17:59

云服務云欺詐云犯罪

2011-11-08 08:14:40

WLANWi-Fi

2011-03-10 10:04:20

Ntdsutil

2009-06-05 09:45:44

Struts優缺點開源

2012-06-19 10:16:04

2011-04-28 13:39:05

RapierPocket PC 2Windows Mob

2025-08-05 03:00:00

AI人工智能AI風險

2022-03-18 13:50:06

區塊鏈加密貨幣去中心化

2013-08-13 09:07:20

大數據
點贊
收藏

51CTO技術棧公眾號

日韩黄色碟片| 国产私人尤物无码不卡| 国内自拍视频一区二区三区| 精品免费一区二区三区| 亚洲国产成人精品无码区99| 台湾av在线二三区观看| 男女性色大片免费观看一区二区| 久久久久999| 日韩av手机在线播放| 成人自拍视频网| 亚洲最色的网站| 欧美日韩中文国产一区发布| 国产麻豆免费观看| 国产精品一国产精品k频道56| 国产亚洲精品日韩| 午夜诱惑痒痒网| 国产资源在线观看入口av| 国产欧美综合在线| 国产福利一区二区三区在线观看| 无码人妻丰满熟妇奶水区码| 欧美理论在线| 色噜噜国产精品视频一区二区| 制服丝袜av在线| 欧美高清你懂的| 欧美天堂在线观看| 一区二区三区欧美成人| 香蕉av一区二区三区| 日韩av不卡在线观看| 久久久久久亚洲| 中文字幕无码日韩专区免费| 西野翔中文久久精品字幕| 日韩一区二区三区在线观看| 男女视频一区二区三区| 91制片在线观看| 亚洲蜜臀av乱码久久精品蜜桃| 欧美日韩一区在线播放| 亚洲aⅴ乱码精品成人区| 国内精品写真在线观看| 国产成人一区三区| 看片网址国产福利av中文字幕| 亚洲国产精品久久久久蝴蝶传媒| 伊人av综合网| 亚洲一区二区观看| 久久久久观看| 精品成人一区二区三区四区| 亚洲精品乱码久久久久久动漫| 精品日韩视频| 色94色欧美sute亚洲线路一ni| 色欲色香天天天综合网www| 污污的网站在线免费观看| 中文字幕一区二区三区蜜月| 日韩精品欧美一区二区三区| 久热av在线| 91麻豆精品视频| 久久国产精品99久久久久久丝袜| 亚洲第一页视频| 国产高清视频一区| 99re热精品| www五月婷婷| 国产精品一二三四| 亚洲在线一区二区| 草草视频在线播放| 国产成人av电影在线观看| 91精品国产综合久久久久久丝袜| 99在线小视频| 成人中文字幕电影| 国产精品露出视频| 亚洲色图另类小说| 久久老女人爱爱| 日韩成人av电影在线| 成年午夜在线| 日韩美女啊v在线免费观看| 成年人免费观看的视频| a级影片在线| 亚洲精品日韩一| 成人精品视频在线播放| 人人草在线视频| 欧洲在线/亚洲| 亚洲娇小娇小娇小| 日本久久伊人| 日韩国产在线看| 亚洲一区二区观看| 国产精品88久久久久久| 欧美成人精品在线观看| 国产 日韩 欧美 成人| 国产情侣一区| 国产精品旅馆在线| av网站免费播放| 9人人澡人人爽人人精品| 日本不卡在线播放| 免费网站成人| 亚洲一级二级三级| 日韩有码免费视频| 亚洲日韩中文字幕一区| 欧美一级精品大片| 老鸭窝一区二区| 四虎8848精品成人免费网站| 欧美激情a∨在线视频播放| 五月婷婷色丁香| 精品亚洲免费视频| 国产无套精品一区二区| h视频在线播放| 亚洲制服欧美中文字幕中文字幕| 欧美亚洲国产成人| 91丨精品丨国产| 日韩精品福利在线| 一区二区三区影视| 性8sex亚洲区入口| 亚洲一区二区自拍| 精华区一区二区三区| 亚洲欧美成人一区二区三区| 免费黄色福利视频| 日韩精品视频一区二区三区| 亚洲天堂av在线免费观看| 三级在线观看免费大全| 久久大逼视频| 国产精品美女黄网| 成人在线播放免费观看| 色哟哟在线观看一区二区三区| 欧美高清精品一区二区| 深爱激情久久| 国内精久久久久久久久久人| 中文字幕欧美人妻精品| 99这里都是精品| 国产一级黄色录像片| 欧美日韩在线精品一区二区三区激情综合| 日韩亚洲国产中文字幕欧美| 色屁屁草草影院ccyy.com| 99精品免费网| 豆国产97在线| 国产淫片在线观看| 欧美三级韩国三级日本三斤| 人妻一区二区视频| 久久国产88| 国产精品免费在线播放| av网站网址在线观看| 欧美日韩三级一区| 中文字幕黄色网址| 久久亚洲国产精品一区二区| 国内成+人亚洲| a级片在线免费| 日韩精品专区在线影院观看| 精品国产视频在线观看| 久久国产尿小便嘘嘘| 日韩精品一区二区三区四区五区| 午夜久久中文| 日韩黄色av网站| 成人午夜视频在线播放| 久久综合久久久久88| 欧美 日本 亚洲| 欧美自拍视频| 538国产精品一区二区在线| 日本免费一区视频| 婷婷成人激情在线网| 国产+高潮+白浆+无码| 亚洲视频免费| 国产一区二区不卡视频| ririsao久久精品一区| 欧美精品一区二区精品网| 久久中文字幕无码| av一本久道久久综合久久鬼色| 成人免费观看cn| 五月综合久久| 国产精品久久中文| 国产欧美黑人| 日韩免费高清av| 国产在线拍揄自揄拍无码视频| 成人午夜电影久久影院| 黄色国产一级视频| 亚洲尤物av| 国产日韩换脸av一区在线观看| 137大胆人体在线观看| 在线播放欧美女士性生活| 九九免费精品视频| 91在线精品一区二区| 国产三区在线视频| 欧美1级片网站| 高清av免费一区中文字幕| 美女av在线免费看| 亚洲天堂av在线播放| 国产伦一区二区| 亚洲h精品动漫在线观看| 国产精品九九九九九| 美女视频网站久久| 欧美国产视频一区| 精品久久久中文字幕| 成人精品久久一区二区三区| 高清电影在线观看免费| 亚洲天堂av网| 亚洲高清视频在线播放| 色综合久久中文综合久久牛| 一本色道久久88| 成人免费va视频| 国产精品人人爽人人爽| 欧美片第1页综合| 蜜桃成人在线| 亚洲日日夜夜| 日本精品va在线观看| 美女国产在线| 亚洲精品在线看| 国产色综合视频| 色综合久久久久综合体| 欧美色图亚洲天堂| 国产欧美日韩在线视频| 第一页在线视频| 日本不卡视频在线观看| 精品视频在线观看一区| 97偷自拍亚洲综合二区| 精品国产91亚洲一区二区三区www 精品国产_亚洲人成在线 | 裸模一区二区三区免费| 91丨精品丨国产| 日产精品久久久一区二区福利| 国产午夜精品久久久久免费视| 日韩精品日韩在线观看| www.激情五月| 欧美日韩精品一区二区| 中文字幕av影院| 亚洲图片有声小说| 极品色av影院| 国产日产欧美一区二区视频| 亚洲一区二区在线免费| 国产精品影音先锋| 中文字幕 日韩 欧美| 亚洲欧美日本国产专区一区| 男女激情免费视频| 欧美欧美全黄| 亚洲五码在线观看视频| 66视频精品| 亚洲人成人77777线观看| 美女少妇全过程你懂的久久| 国产精品一区二区不卡视频| 国产精品成人**免费视频| 国产精品精品视频| 欧美大胆成人| 日韩av第一页| 中文在线免费二区三区| 久久男人av资源网站| 羞羞视频在线观看不卡| 美女黄色丝袜一区| 超碰个人在线| 九九精品在线视频| av网址在线看| 九九热视频这里只有精品| 大片免费在线看视频| www.欧美免费| 麻豆免费在线观看| 裸体女人亚洲精品一区| 国产色在线观看| 久久av.com| av网站免费在线观看| 欧美大片在线看| heyzo高清中文字幕在线| 欧美精品福利在线| 2018av在线| 欧美在线视频一区| 日韩免费va| 国产精品视频区1| 日韩免费在线电影| 亚洲伊人一本大道中文字幕| 日韩欧美中文在线观看| 99中文字幕| 欧美一区二区三区红桃小说| 欧美成人一区二区在线| 国产va免费精品观看精品视频| 日韩美女一区| 亚洲乱码电影| 精品少妇在线视频| 久久激情一区| 国产一区二区在线免费播放| 激情综合色综合久久| 久久久久99人妻一区二区三区| 成人免费高清在线| 老司机福利av| 中文字幕在线观看一区二区| 欧美日韩精品亚洲精品| 精品国产精品三级精品av网址| 亚洲国产成人精品女人久久| 欧美日韩国产色站一区二区三区| 国产黄色小视频在线观看| 亚洲国产日韩欧美综合久久| 欧美日韩在线精品一区二区三区激情综| 一区二区三区高清国产| 国产网站在线免费观看| 韩国美女主播一区| 巨胸喷奶水www久久久免费动漫| 91九色极品视频| 小说区图片区色综合区| 一区二区三区四区视频在线| 在线观看一区视频| 日本www.色| 丁香婷婷综合激情五月色| 麻豆精品免费视频| 亚洲精品伦理在线| 日韩欧美在线观看免费| 欧美一区二区三区视频在线观看| 亚洲 欧美 激情 小说 另类| 久久精品国产清自在天天线 | 亚洲丰满少妇videoshd| 久久精品99北条麻妃| 日韩精品中文字幕在线一区| 超碰国产在线| 韩国福利视频一区| 精品国产18久久久久久二百| 欧美极品一区| 黄色亚洲精品| 久久精品国产99久久99久久久| 久久―日本道色综合久久| 免费在线观看一级片| 日本精品一级二级| 色屁屁草草影院ccyycom| x99av成人免费| 3d性欧美动漫精品xxxx软件| 99re视频| 亚洲午夜精品一区二区国产| 欧美亚洲日本在线观看| kk眼镜猥琐国模调教系列一区二区| 性爱在线免费视频| 色综合天天综合网天天看片| 亚洲第一成人av| 久久在线视频在线| а√天堂资源国产精品| 欧美人与物videos另类| 亚洲国产一区二区三区高清 | www久久久久| 久久精品国产亚洲av高清色欲| 欧美二区在线观看| 国产福利在线| 日韩av三级在线观看| 免费成人三级| 久无码久无码av无码| 国产九九视频一区二区三区| 国产三级aaa| 欧美日韩在线综合| 国产视频精选在线| 人人澡人人澡人人看欧美| 伦理一区二区三区| 热99这里只有精品| 成人黄色777网| 国产精品99re| 亚洲成人动漫在线播放| 国产啊啊啊视频在线观看| 97超碰资源| 亚洲一级网站| 久久人妻少妇嫩草av无码专区| 亚洲电影激情视频网站| 免费观看a视频| 5566日本婷婷色中文字幕97| 人人网欧美视频| 黄www在线观看| 国产亚洲精品精华液| 欧美日韩在线视频播放| 在线精品国产欧美| 免费成人高清在线视频| 中国成人亚色综合网站| 国产一区久久久| 久久久久久激情| 亚洲国产精品热久久| 亚洲黄色网址| 天天综合色天天综合色hd| 麻豆91精品91久久久的内涵| 中文乱码字幕高清一区二区| 91精品国产黑色紧身裤美女| 性爱视频在线播放| 国内不卡一区二区三区| 日韩国产欧美在线视频| 亚洲天堂网av在线| 欧美草草影院在线视频| rebdb初裸写真在线观看| 免费精品视频一区| 麻豆一区二区在线| 欧美成人aaa片一区国产精品| 精品国免费一区二区三区| 在线人成日本视频| 亚洲人成网站在线播放2019| 国产成都精品91一区二区三| www.国产高清| 日韩中文字在线| 成人涩涩网站| 黄色片在线免费| 日韩美女精品在线| 四虎影视精品成人| 91精品久久久久久久久久另类| 欧美xxx在线观看| mm131美女视频| 91精品在线麻豆| 在线毛片观看| 伊人久久大香线蕉精品| gogogo免费视频观看亚洲一| 中文在线免费看视频| 欧美激情二区三区| 国产99久久| 国产人妻精品午夜福利免费| 91福利区一区二区三区| 在线看女人毛片| 日日夜夜精品网站| av在线免费不卡| 国产精品怡红院| 日韩av免费在线看| 亚洲激情另类| 亚洲综合视频网站|