java魔法類之Unsafe介紹


前言

Unsafe類位於sun.misc包下,它是java實現高並發的基礎,通過它可以執行一些不安全的操作,如像C語言一樣直接操作內存資源,
它提供的這些方法增強了java對底層資源的操作能力,但同時也增加了程序出錯的風險,所以對它的使用一定要慎重。

核心功能介紹

Unsafe提供的API大致可分為內存操作、CAS、Class相關、對象操作、線程調度、系統信息獲取、內存屏障相關、數組相關等。下面介紹幾個方法的使用。

獲取Unsafe對象

public final class Unsafe {
  // 單例對象
  private static final Unsafe theUnsafe;

  private Unsafe() {
  }
  @CallerSensitive
  public static Unsafe getUnsafe() {
    Class var0 = Reflection.getCallerClass();
    // 僅在引導類加載器`BootstrapClassLoader`加載時才合法
    if(!VM.isSystemDomainLoader(var0.getClassLoader())) {    
      throw new SecurityException("Unsafe");
    } else {
      return theUnsafe;
    }
  }

 static {
    theUnsafe = new Unsafe();
  }
}

Unsafe提供的getUnsafe()方法只能被根類加載器加載的類所調用,也就是jdk內部的類。我們可以通過反射來獲取Unsafe對象。

import java.lang.reflect.Field;
import sun.misc.Unsafe;

public class TestUnSafe2 {

  public static void main(String[] args)
      throws NoSuchFieldException, IllegalAccessException {
    Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
    theUnsafe.setAccessible(true);
    Unsafe unsafe = (Unsafe) theUnsafe.get(null);
    System.out.println(unsafe);
  }

}

CAS

CAS(compareAndSwap)即比較並替換,是實現並發算法時常用到的一種技術,CAS操作包含三個參數,要修改變量的內存位置、預期原值、要修改為的值,
如果變量的值和預期原值相等,就修改為新值,否則不做處理。CAS底層為一條原子指令cmpxchg,可以保證原子性,
Unsafe提供的CAS方法如compareAndSwapInt底層就是CPU指令cmpxchg。

/**
	*  CAS
  * @param o         包含要修改field的對象
  * @param offset    對象中某field的偏移量
  * @param expected  期望值
  * @param update    更新值
  * @return          true | false
  */
public final native boolean compareAndSwapObject(Object o, long offset,  Object expected, Object update);

public final native boolean compareAndSwapInt(Object o, long offset, int expected,int update);
  
public final native boolean compareAndSwapLong(Object o, long offset, long expected, long update);

CAS在java並發包中的原子類如AtomicInteger,AQS(AbstractQueuedSynchronizer),ConcurrentHashMap等實現中都有廣泛的使用。

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;
}

在AtomicInteger的實現中,靜態屬性valueOffset即為屬性value的內存偏移地址,在靜態代碼塊中通過Unsafe的objectFieldOffset方法對valueOffset賦值。
在AtomicInteger中提供的線程安全方法中,通過屬性valueOffset可以定位到屬性value的內存地址,從而可以根據CAS實現對value屬性的原子操作。

上圖為某個AtomicInteger對象自增操作前后的內存示意圖,對象的基地址baseAddress=“0x110000”,通過baseAddress+valueOffset得到value的
內存地址valueAddress=“0x11000c”;然后通過CAS進行原子性的更新操作,成功則返回,否則繼續重試,直到更新成功為止。

對象操作

此部分主要包含對象成員屬性相關操作及非常規的對象實例化方式等相關方法。

//返回對象成員屬性在內存地址相對於此對象的內存地址的偏移量
public native long objectFieldOffset(Field f);
//獲得給定對象的指定地址偏移量的值,與此類似操作還有:getInt,getDouble,getLong,getChar等
public native Object getObject(Object o, long offset);
//給定對象的指定地址偏移量設值,與此類似操作還有:putInt,putDouble,putLong,putChar等
public native void putObject(Object o, long offset, Object x);
//從對象的指定偏移量處獲取變量的引用,使用volatile的加載語義
public native Object getObjectVolatile(Object o, long offset);
//存儲變量的引用到對象的指定的偏移量處,使用volatile的存儲語義
public native void putObjectVolatile(Object o, long offset, Object x);
//有序、延遲版本的putObjectVolatile方法,不保證值的改變被其他線程立即看到。只有在field被volatile修飾符修飾時有效
public native void putOrderedObject(Object o, long offset, Object x);
//繞過構造方法、初始化代碼來創建對象
public native Object allocateInstance(Class<?> cls) throws InstantiationException;

  • 常規對象實例化方式:我們通常所用到的創建對象的方式,從本質上來講,都是通過new機制來實現對象的創建。
    但是,new機制有個特點就是當類只提供有參的構造器且沒有顯式聲明無參構造器時,必須使用有參構造器並傳遞相應個數的參數進行對象構造,
  • 非常規的實例化方式:而Unsafe中提供的allocateInstance方法,僅通過Class對象就可以創建此類的實例對象,
    而且不需要調用其構造器、初始化代碼、JVM安全檢查等。它抑制修飾符檢測,也就是即使構造器是private修飾的也能通過此方法實例化。
    由於這種特性,allocateInstance在java.lang.invoke、Objenesis(提供繞過類構造器的對象生成方式)、Gson(反序列化時用到)中都有相應的使用。
public class User {

  private User() {
    System.out.println("User.Constructor");
  }

  @Override
  public String toString() {
    return "User.toString()";
  }
}

定義一個構造器為私有的類

import java.lang.reflect.Field;
import sun.misc.Unsafe;

public class TestUnSafe {

  public static void main(String[] args) throws InstantiationException {
    Unsafe unsafe = getUnsafe();
    System.out.println(unsafe.allocateInstance(User.class));//User.toString()
  }

  private static Unsafe getUnsafe() {
    try {
      Class<Unsafe> unsafeClass = Unsafe.class;
      Field theUnsafe = unsafeClass.getDeclaredField("theUnsafe");
      theUnsafe.setAccessible(true);
      return unsafeClass.cast(theUnsafe.get(null));
    } catch (NoSuchFieldException | IllegalAccessException e) {
      e.printStackTrace();
    }
    return null;
  }


}

通過Unsafe的allocateInstance()方法來創建對象,根據輸出結果發現確實可以繞過構造器。

Gson(一個解析json的庫)中的UnsafeAllocator在反序列化創建對象時會使用到該方法。

參考

Java魔法類:Unsafe應用解析


免責聲明!

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



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