精品欧美一区二区三区在线观看 _久久久久国色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技術棧公眾號

亚洲成人激情av| 日韩精品成人一区二区在线| 91精品国产高清一区二区三区| 椎名由奈jux491在线播放| 一级黄色免费片| 欧美全黄视频| 日韩欧美色综合| 国产无限制自拍| 欧美新色视频| 久久精品国产免费看久久精品| 久久久成人精品| 日本久久久久久久久久| 欧美美女日韩| 亚洲免费观看高清完整版在线观看| 成人av影视在线| 日韩一区二区视频在线| 日韩三级在线| 亚洲国产精品yw在线观看| 精品福利视频一区二区三区| 亚洲狠狠婷婷综合久久久| 国产一区二区波多野结衣| 国模大胆一区二区三区| 日韩国产欧美精品在线| 网站一区二区三区| 黄色成人在线网| 国产视频一区二区在线观看| 亚洲淫片在线视频| 99久热在线精品996热是什么| 日韩在线观看一区 | 狠狠色综合日日| 欧美国产一区二区三区| 久久久久亚洲av成人无码电影| 国产精品亚洲综合在线观看| 日韩欧美中文第一页| 艳母动漫在线免费观看| 欧美zozo| 成人性视频网站| 国产一区二区丝袜| 久久国产视频精品| 国内精品亚洲| 国产亚洲精品va在线观看| 麻豆短视频在线观看| yiren22亚洲综合| 午夜伦理一区二区| 国产资源第一页| yw193.com尤物在线| 成人福利视频在线看| 国产精品美女久久久久久免费| 欧美激情国产精品免费| 日韩中文欧美| 亚洲视频在线免费观看| 艳妇乳肉亭妇荡乳av| 欧美电影在线观看一区| 欧美区视频在线观看| 北条麻妃视频在线| www.成人爱| 精品美女久久久久久免费| 欧美 日韩 国产精品| 精品国产丝袜高跟鞋| 欧美激情一二三区| 日韩欧美视频一区二区| 四虎精品成人免费网站| jlzzjlzz国产精品久久| 粉嫩av一区二区三区免费观看| 国产又粗又黄视频| 精品一区二区三区免费| 成人国产精品久久久| 波多野结衣不卡| 丝袜亚洲另类丝袜在线| 日本精品久久久久久久| 黄色一级片在线免费观看| 91精品综合久久久久久久久久久 | 福利视频999| 亚州欧美在线| 欧美日韩高清一区二区不卡| 中文字幕在线导航| 日本肉肉一区| 欧美日韩成人综合在线一区二区| 苍井空浴缸大战猛男120分钟| 热色播在线视频| 一本色道久久综合亚洲精品按摩| 加勒比成人在线| a级片免费在线观看| 欧美日韩亚洲高清| 色综合av综合无码综合网站| 欧美黄色三级| 欧美三级韩国三级日本三斤| 五月天开心婷婷| 亚洲国产精品免费视频| 欧美xxx久久| 国产精品福利导航| 精品中文字幕一区二区三区av| 日韩黄色高清视频| 亚洲日本精品视频| 国产韩日影视精品| 欧美超级免费视 在线| 国产亚洲成人精品| 亚洲欧美日韩专区| 国产精品自产拍在线观看| 国产视频手机在线| 99精品国产视频| 欧美久久久久久久| 国产cdts系列另类在线观看| 亚洲一区免费在线观看| 国产裸体舞一区二区三区| 精品久久福利| 91精品国产aⅴ一区二区| 国产高潮视频在线观看| 成人免费在线观看av| 九九热精品视频国产| 成人免费区一区二区三区| 免费久久99精品国产| 92国产精品视频| 天天在线女人的天堂视频| 欧美韩国一区二区| 91午夜在线观看| 国内欧美日韩| 日韩av一区二区在线观看| 精品手机在线视频| 日韩视频一区| 成人免费视频在线观看超级碰| 亚洲欧美色视频| 亚洲视频在线一区观看| 国产黄色一级网站| 国产情侣一区在线| 国产午夜精品视频| 国产一级生活片| 日本女优在线视频一区二区| 国产伦精品一区二区三区在线| 1769在线观看| 欧美日韩人人澡狠狠躁视频| 日本成人在线免费观看| 激情五月综合| 91精品国产91| www.爱爱.com| 国产欧美综合在线| 久久精品国产sm调教网站演员| 精品久久福利| 亚洲性视频网站| 国产成人精品片| 国产一区二区精品久久99| 日本精品视频一区| 免费成人在线电影| 亚洲精品一区二区三区影院| 韩国一级黄色录像| 亚洲精选久久| 成人黄色在线免费观看| 黄色av电影在线观看| 欧美三级一区二区| 国产特级黄色录像| 欧美日韩a区| 亚洲一区免费网站| 超碰电影在线播放| 在线观看91视频| 国产特级黄色录像| 亚洲欧美视频一区二区三区| 国产精品视频免费一区| wwwww亚洲| 日韩欧美激情一区| 欧美黄色免费看| 国产成人精品午夜视频免费| 懂色av一区二区三区四区五区| 91成人在线| 国产一区二区精品丝袜| 欧美brazzers| 国产三级欧美三级日产三级99 | 爆操欧美美女| 91精品婷婷国产综合久久性色| 能直接看的av| 久久激情五月婷婷| 2025韩国大尺度电影| 91成人福利社区| 久久精品国产成人精品| 97超碰资源站| 国产精品视频免费看| 欧美三级理论片| 精品成人影院| 国产精品午夜国产小视频| 国产人成在线视频| 在线观看一区二区视频| 人人干在线观看| 国产另类ts人妖一区二区| 一本一本a久久| 9.1麻豆精品| 国内精品久久久久伊人av| 色在线免费视频| 在线一区二区三区四区五区| 免费成人美女女在线观看| 国产精品18久久久久久久久 | 国产日产欧美a一级在线| 国产在线中文字幕| 欧美二区三区的天堂| 成年人av电影| 91丨porny丨户外露出| av网站在线不卡| 欧美精品一卡| 秋霞毛片久久久久久久久| 亚洲狼人在线| 国语自产在线不卡| 91短视频版在线观看www免费| 欧美久久久久免费| 国产成人在线视频观看| 亚洲伦理在线精品| 亚洲天堂岛国片| av电影在线观看不卡| 亚洲第一色av| 日日夜夜免费精品| 性一交一乱一伧国产女士spa| 成人综合一区| 九色一区二区| 成人知道污网站| 成人性生交大片免费看视频直播 | 一区二区免费不卡在线| 欧美lavv| 国产一区二区三区亚洲| 成人黄色免费网站在线观看| 亚洲成a人片| 性日韩欧美在线视频| 91精选在线| 中文字幕久久精品| 国产精品一区二区三区四区色| 亚洲国产精品va在线看黑人 | 成人一区二区视频| 黄色小视频免费网站| 天堂一区二区在线免费观看| 国产精品12345| 综合激情网站| 日韩视频在线观看视频| 久久综合国产| 日韩一本精品| 欧美日韩一区二区综合| 日本不卡二区高清三区| 香蕉久久夜色精品国产使用方法 | 日韩欧美在线字幕| 99热在线观看免费精品| 亚洲成人免费影院| 久久久久无码国产精品不卡| 亚洲三级小视频| 波多野结衣久久久久| 国产精品国产三级国产| 日本黄区免费视频观看| 中文av字幕一区| 九一在线免费观看| 国产精品初高中害羞小美女文| jizz日本在线播放| 国产精品久久久久天堂| 美女三级黄色片| 亚洲美女淫视频| 日韩影院一区二区| 一区二区理论电影在线观看| 国产探花在线播放| 亚洲国产综合在线| 亚洲精品www久久久久久| 欧美日韩国产丝袜另类| 日韩黄色在线播放| 91精品福利视频| 中文字幕av片| 7777女厕盗摄久久久| 精品人妻aV中文字幕乱码色欲| 欧美一区二区久久久| 丁香六月天婷婷| 亚洲免费精彩视频| 在线看免费av| 欧美精品一区二区三区国产精品| 污网站在线免费看| 7m第一福利500精品视频| 欧美电影免费看| 成人午夜激情网| 大陆精大陆国产国语精品| 免费看成人片| 色综合色综合| 欧美激情亚洲天堂| 香蕉久久夜色精品国产| wwwwww.色| 国产高清不卡一区| 性久久久久久久久久| 国产精品久久久一本精品 | 欧美亚洲国产一卡| 国产99对白在线播放| 日韩av在线影院| av在线电影播放| 久久99久久99精品中文字幕| 三妻四妾完整版在线观看电视剧| 国产精品视频久| 国产视频一区二区在线播放| 国产伦精品一区二区三| 日韩在线第七页| 日本欧美黄色片| 麻豆精品久久久| 久久精品女同亚洲女同13| 国产女同性恋一区二区| 久久免费视频播放| 欧美日韩在线播放一区| 欧美一级在线免费观看| 国产亚洲精品一区二区| 日本一本在线免费福利| 国产精品久久久久久久一区探花| 亚洲国产视频二区| 亚洲午夜精品久久| 国产农村妇女毛片精品久久莱园子 | 欧美成人dvd在线视频| 性欧美69xoxoxoxo| 成人小视频在线看| 成人午夜伦理影院| 最新黄色av网址| 欧美日韩国产页| www.av黄色| 精品国产一区二区三区久久狼5月| 91视频免费在观看| 日本vs亚洲vs韩国一区三区| 亚洲无人区码一码二码三码| 亚洲国产精品t66y| av黄色在线播放| 欧美va亚洲va香蕉在线| 日本三级在线播放完整版| 欧美中文字幕在线视频| 国产主播性色av福利精品一区| gogogo免费高清日本写真| 日韩av一区二区三区四区| 在线视频 日韩| 悠悠色在线精品| 91久久久久国产一区二区| 亚洲色图狂野欧美| 亚洲人成在线网站| 国产精品区一区二区三在线播放| 欧美1区2区| 天天操精品视频| 国产精品美女久久久久久久网站| 黄色片中文字幕| 日韩电影中文字幕在线| 高清电影在线免费观看| 亚洲v日韩v综合v精品v| 日韩电影免费网址| 男人搞女人网站| 日本一区二区三区四区在线视频 | 免费观看在线午夜影视| 国产精品色视频| 欧美日韩中字| 免费男同深夜夜行网站| 91首页免费视频| 探花视频在线观看| 亚洲欧美国产高清va在线播| 美女高潮视频在线看| 精品欧美一区二区在线观看视频| 亚洲三级毛片| 添女人荫蒂视频| 日韩欧美国产网站| 精品美女视频在线观看免费软件| 69国产精品成人在线播放| 亚洲小说图片| 少妇激情一区二区三区| 国产精品久久一级| 国产女主播福利| 欧美激情xxxxx| 国产精品极品在线观看| www.av中文字幕| 国产亚洲一区二区三区四区| 成人黄色三级视频| 日韩中文字幕网站| 精品国产一区二区三区性色av| 日韩不卡一二区| 国产成人综合在线播放| 日韩成年人视频| 亚洲图片在线综合| 日韩五码电影| 日韩精品在线观看av| 97久久久精品综合88久久| 国产污视频网站| 久久精品国产99国产精品澳门| 中文字幕亚洲在线观看| 免费看又黄又无码的网站| 久久久不卡网国产精品二区| 一级二级三级视频| 欧美人在线观看| 影视先锋久久| 日韩成人av免费| 精品国产户外野外| 天天综合视频在线观看| 国产激情美女久久久久久吹潮| 国产亚洲精品久久久久婷婷瑜伽| 亚洲自拍偷拍一区二区| 精品婷婷伊人一区三区三| 性国产高清在线观看| 奇米影视首页 狠狠色丁香婷婷久久综合 | 2019中文字幕在线观看| 欧美3p视频| 在线免费观看污视频| 欧美色图免费看| 国产精品69xx| 亚洲精品在线观看免费| 懂色av一区二区在线播放| 中文字幕在线天堂| 久久久久久久国产| 日本久久一二三四| 在线免费看黄色片| 欧美猛男超大videosgay| 国产va在线视频| 一级黄色免费在线观看| 久久久久久久网| 欧美熟妇交换久久久久久分类 |