sun.misc.Unsafe中一些常用方法記錄


sun.misc.Unsafe中一些常用方法記錄

前情摘要

sun公司提供了可以用於直接操作內存的類,這個類就是sun.misc.Unsafe。因為Java本身是不會涉及到直接操作內存的,Java API也沒有提供這些操作,內存管理全部交給虛擬機來做。Sun之所以提供這個類,因為有些功能現有的Java API滿足不了,如果沒有這個類,可能就沒有現在原子類,J.U.C包了,也許也沒了各種Concurrent Collection類,可能也沒了NIO的堆外內存,所以這個類十分的有用,並且很重要,Sun也沒有開放這個類的源代碼,並且對它的使用也做了一些限制。

通過反編譯看到這個類中,幾乎所有的方法都是native修飾的,即使其他非native修飾的,最后也是在調用本類的其他native方法。

Unsafe提供了一個static修飾的靜態方法,用來獲取這個類的實例,這是一個單例。

   @CallerSensitive
   public static Unsafe getUnsafe() {
        Class var0 = Reflection.getCallerClass();
        if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
            throw new SecurityException("Unsafe");
        } else {
            return theUnsafe;
        }
    }

這個getUnsafe()方法並不是直接返回Unsafe的實例,而是做了校驗。通過校驗直接使用getUnsafe這個方法的類的類加載器是否為Bootstrap類加載器,來做安全檢查。由於Java API中的類都是由Bootstrap類加載器加載的,所以是可以直接調用這個方法來獲取Unsafe的實例。不過如果要在自己的代碼中使用Unsafe,則需要通過反射修改theUnsafe字段的訪問修飾符,然后獲取Unsafe的實例。由於Unsafe可以直接操作內存,所以存在很大的風險,使用時需要特別謹慎!

想閱讀下關於原子類和J.U.C包下的一些實現,發現這些經常使用的方法,記錄下每個方法的具體含義,不然沒法理解整個客戶端方法的行為。

整個Unsafe類中共分為以下幾類操作:

  • 底層內存信息相關的 比如內存頁大小
  • 操作對象及其字段 如:實例化對象,獲取實例域的偏移量
  • 操作類以及靜態字段 如:定義一個類,獲取靜態字段的偏移量
  • 操作數據對象
  • 同步操作 提供操作監控器和CAS的支持
  • 內存操作 主要是內存的分配,復制,銷毀等 NIO中用到的多

JUC和原子類以及並發集合中主要用到以下這些方法

Unsafe.putObject()

將一個引用類型的值存入到給定對象的變量中,這個方法的參數列表為putObject(Object o, long offset, Object x),這里就是將對象x的引用,存到對象o的偏移量為offset的變量上。要注意這個操作不保證內存可見性,也就是說對對象o的指定字段的更新,並不會在多線程環境下被其他線程發現值的變動。所以提供了一個具有volatile語義的方法putObjectVolatile(),這個操作具有volatile語義的store語義,並不具備load的語義,如果想在獲取時具備load的語義,可以使用getObjectVolatile()

Unsafe.park()

此方法主要用於阻塞當前線程,方法的參數為park(boolean absolute, long time),如果absolute為false,time為0,則表示一直阻塞,直至unpark方法被調用,或者被中斷。如果absolute為false,time不為0,則表示為給定的納秒過后,中斷阻塞,相應的線程可以被系統調度。如果absolute為true,time的單位為毫秒,不過這是一個絕對時間,Epoch Time—— Unix紀元時間 1970.1.1 零時,absolute表示的絕對是指基於這個時間的絕對時間,也就是說park(true, System.currentTimeMillis()+N)這種寫法才是對的,不然輸入的任意數字都沒有作用的,不會起到阻塞線程的效果。

會將線程一直阻塞,直至以下情況發生:

  • 當相應的unpark方法在park方法調用前被調用,park方法調用會被立即返回
  • 當相應的unpark方法在park方法后被調用,park方法返回
  • 在調用park方法的前后,如果檢測到線程已經被設置為中斷,則park方法立即返回
  • absolute為false並且time不為0,所給的納秒已經過了
  • absolute為true,並且所給的時間(必須是在Epoch紀元時間的基礎上,通常取當前距離紀元時間的毫秒數加上希望阻塞時間毫秒數)毫秒已經用完
  • 無理由返回

Unsafe.unpark()

將指定的線程從park阻塞狀態中恢復過來,方法僅有一個參數unpark(Thraed thread),要確保thread對象沒有被銷毀,也就是要檢查不為null

Unsafe.getObjectVolatile()

獲取所給對象的所給變量的值,使用volatile語義的load語義,會在實際獲取這個值的時候從主存中加載,不會使用CPU緩存中的,總能確保獲取到的是有效的值。 getObjectVolatile(Object o, long offset)

Unsafe.getInt()

有三個重載方法getInt(Object o, long offset)getInt(long address)getIntVolatile(long address),都是從指定的位置獲取變量的值,只不過第一個的offset是相對於對象o的相對偏移量,第二個address是絕對地址偏移量。如果第一個方法中o為null是,offset也會被作為絕對偏移量。第三個則是帶有volatile語義的load讀操作。

Unsafe.putInt()

同樣有三個在用的重載方法,參數也差不多同putInt類似,含義則是相反,用於對指定內存地址的變量賦值。

putInt(Object o, long offset, int x)putInt(long address)putIntVolatile(Object o, long offset, int x),最后一個是Volatile版本的putInt方法。

Unsafe.objectFieldOffset()

這個方法的作用是用來獲取指定對象的域的偏移量,這個是指這個字段在內存中的位置相對於這個對象在內存中的起始地址的偏移量,也就是隔了多遠,有了這個值,后續就能直接定位到這個域的內存地址,然后獲取其中的值去操作。注意,這個方法只適用於域為非static修飾的,static修飾的域需要使用Unsafe.staticFieldOffset()

Usage:

long offset = unsafe.objectFieldOffset(Field var) 方法結收一個java.lang.reflect.Field對象。

靜態域的偏移量也是一樣的使用,方法名不同。

Unsafe.compareAndSwapObject()

以CAS的方式來更新一個引用類型的字段值,如果更新成功則返回true,否則返回false。

Usage:

boolean casResult = unsafe.compareAndSwapObject(object,offset,expected,update);

這個方法本身不會有自旋行為,直接做CAS操作,如果失敗立即返回。通常是我們在代碼中通過無限循環實現CAS的自旋,並且需要被更新的域一般都是用volatile修飾的,不然多線程環境下無法保證正確性。

如果想通過此方式來CAS操作靜態域的值,第一個參數為靜態域所在的Class對象。

Unsafe.compareAndSwapInt()

和上面的方法作用一樣,只不過是用來操作int類型的變量

Unsafe.putOrderedInt()

Unsafe.putIntVolatile()的有序版本/延遲版本,只保證最終將制定的變量更新為新的值。

Unsafe.getAndSetInt()

這個不屬於native方法,是用getIntVolatilecompareAndSwapInt兩個方法組合,對指定的變量設置為新的值,不過會返回設置新值前的舊值,而不是無條件直接設置,在競爭的條件下,這樣就需要在循環中不斷做CAS。

Unsafe.getAndAddInt()

這個同樣不屬於native方法,和上面差不多,只不過是在舊值的基礎上做了個加新值的操作


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM