天天看點

【Java】Unsafe源碼走讀及實戰應用

前言

  • Unsafe顧名思義,它不安全,要小心使用
  • Unsafe可以控制對象的記憶體申請和釋放,可以對記憶體通路進行控制
  • Unsafe本身僅是為JDK服務的,不推薦應用程式直接使用,且JDK可能随時會改動它

以下示範的JDK版本:1.8

1 使用

部分源碼:

Unsafe:
//私有變量
private static final Unsafe theUnsafe;

//私有構造函數
private Unsafe(){}

//私有變量初始化
static {
	......
	theUnsafe = new Unsafe();
	......
}

//公開擷取Unsafe對象
public static Unsafe getUnsafe() {
	Class arg = Reflection.getCallerClass();
	//判斷目前ClassLoader是否是頂層類加載器(null),是以一般情況下,隻有rt.jar中的類可以使用Unsafe對象
	if (!VM.isSystemDomainLoader(arg.getClassLoader())) {
	    throw new SecurityException("Unsafe");
	} else {
	    return theUnsafe;
	}
}
VM:
//判斷ClassLoader是否是頂層類加載器(null)
public static boolean isSystemDomainLoader(ClassLoader arg) {
	return arg == null;
}
           

分析:

  • Unsafe類是final類型,無法被其他類繼承
  • Unsafe構造函數為私有,不能通過正常途徑建立對象
  • Unsafe成員變量theUnsafe為私有,無法被外部通路
  • 唯一的公開方法getUnsafe,限制了隻有rt.jar才能使用(或者是-Xbootclasspath指定class)

那咋辦?隻能靠反射了,通過構造函數或成員變量,都能擷取Unsafe對象:

/**
* 通過私有構造函數Unsafe()擷取Unsafe
* 
* @return
* @throws Exception
*/
public static Unsafe getUnsafeByContructor() throws Exception {
	// 擷取私有無參構造函數Unsafe()
	Constructor<Unsafe> constructor = Unsafe.class.getDeclaredConstructor();
	// 關閉安全檢查,不代表一定可以通路,但一定可以減小安全檢查的資源消耗,常用于反射類功能中
	constructor.setAccessible(true);
	Unsafe unsafe = (Unsafe) constructor.newInstance();
	return unsafe;
}
/**
* 通過私有成員變量theUnsafe擷取Unsafe對象
* 
* @return
* @throws Exception
*/
public static Unsafe getUnsafeByField() throws Exception {
	// 擷取私有成員變量theUnsafe
	Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
	// 關閉安全檢查,不代表一定可以通路,但一定可以減小安全檢查的資源消耗,常用于反射類功能中
	unsafeField.setAccessible(true);
	Unsafe unsafe = (Unsafe) unsafeField.get(null);
	return unsafe;
}
           

2 并發安全

2.1 CAS

一種可以支援并發修改的判斷:

//1:待更新對象;2:記憶體位址-偏移量;3:目前預期值;4:待更新值;
//更新成功傳回true,否則為false
public final native boolean compareAndSwapObject(Object arg0, long arg1, Object arg3, Object arg4);
public final native boolean compareAndSwapInt(Object arg0, long arg1, int arg3, int arg4);
public final native boolean compareAndSwapLong(Object arg0, long arg1, long arg3, long arg5);
           

2.2 volatile

Object類型和8種基礎資料類型的18個Get和Put方法,包含volatile特性:工作記憶體中的值永遠最新;禁止指令重排。

public native Object getObjectVolatile(Object arg0, long arg1);
public native void putObjectVolatile(Object arg0, long arg1, Object arg3);
public native int getIntVolatile(Object arg0, long arg1);
public native void putIntVolatile(Object arg0, long arg1, int arg3);
public native boolean getBooleanVolatile(Object arg0, long arg1);
public native void putBooleanVolatile(Object arg0, long arg1, boolean arg3);
public native byte getByteVolatile(Object arg0, long arg1);
public native void putByteVolatile(Object arg0, long arg1, byte arg3);
public native short getShortVolatile(Object arg0, long arg1);
public native void putShortVolatile(Object arg0, long arg1, short arg3);
public native char getCharVolatile(Object arg0, long arg1);
public native void putCharVolatile(Object arg0, long arg1, char arg3);
public native long getLongVolatile(Object arg0, long arg1);
public native void putLongVolatile(Object arg0, long arg1, long arg3);
public native float getFloatVolatile(Object arg0, long arg1);
public native void putFloatVolatile(Object arg0, long arg1, float arg3);
public native double getDoubleVolatile(Object arg0, long arg1);
public native void putDoubleVolatile(Object arg0, long arg1, double arg3);
           

2.3 CAS+Volatile

//給對象增加指定值:1-對象;2-記憶體位址;3-增加值
public final int getAndAddInt(Object arg0, long arg1, int arg3) {
	int arg4;
	do {
	    arg4 = this.getIntVolatile(arg0, arg1);
	} while (!this.compareAndSwapInt(arg0, arg1, arg4, arg4 + arg3));

	return arg4;
}

public final long getAndAddLong(Object arg0, long arg1, long arg3) {
	......
}
//給對象賦予指定值:1-對象;2-記憶體位址;3-賦予值
public final int getAndSetInt(Object arg0, long arg1, int arg3) {
	int arg4;
	do {
	    arg4 = this.getIntVolatile(arg0, arg1);
	} while (!this.compareAndSwapInt(arg0, arg1, arg4, arg3));

	return arg4;
}

public final long getAndSetLong(Object arg0, long arg1, long arg3) {
	......
}

public final Object getAndSetObject(Object arg0, long arg1, Object arg3) {
	......
}
           

2.4 記憶體屏障

//記憶體屏障,禁止load操作重排序,即屏障前的load操作不能被重排序到屏障後,屏障後的load操作不能被重排序到屏障前
//即a=1;loadFence();b=2;那麼工作記憶體中一定是先發生a=1,然後再發生b=2;
public native void loadFence();

//記憶體屏障,禁止store操作重排序,即屏障前的store操作不能被重排序到屏障後,屏障後的store操作不能被重排序到屏障前
//即a=1;storeFence();b=2;那麼主記憶體中一定是先發生a=1,然後再發生b=2;工作記憶體中,可能先b=2,後a=1;
public native void storeFence();

//記憶體屏障,禁止load、store操作重排序
//即a=1;fullFence();b=2;那麼工作記憶體和主記憶體都是先發生a=1,然後再發生b=2;
public native void fullFence();
           

volatile和記憶體屏障不了解的可以看:java記憶體模型與線程

2.5 有序寫入

//設定對象var1中offset偏移位址var2對應的Object型field的值為指定值var4
public native void putOrderedObject(Object var1, long var2, Object var4);

public native void putOrderedInt(Object var1, long var2, int var4);

public native void putOrderedLong(Object var1, long var2, long var4);
           

3 其他功能

僅了解即可,用到或見到時候便于檢視。

3.1 非堆記憶體操作

//獲得本地指針
public native long getAddress(long address);
//存儲本地指針到給定的記憶體位址
public native void putAddress(long address, long x);
  
//配置設定記憶體
public native long allocateMemory(long bytes);
//重新配置設定記憶體
public native long reallocateMemory(long address, long bytes);

//初始化記憶體内容
public native void setMemory(Object o, long offset, long bytes, byte value);
public void setMemory(long address, long bytes, byte value) {
 setMemory(null, address, bytes, value);
}

//記憶體内容拷貝
public native void copyMemory(Object srcBase, long srcOffset,Object destBase, long destOffset,long bytes);
public void copyMemory(long srcAddress, long destAddress, long bytes) {
 copyMemory(null, srcAddress, null, destAddress, bytes);
}

//釋放記憶體
public native void freeMemory(long address)

//傳回指針的大小,傳回值為4或8
public native int addressSize();
//記憶體頁的大小
public native int pageSize();  
           

3.2 偏移量操作

//靜态屬性存儲配置設定中的位置(偏移位址)。
public native long staticFieldOffset(Field arg0);

//非靜态屬性存儲配置設定中的位置(偏移位址)
public native long objectFieldOffset(Field arg0);

//傳回給定的靜态屬性的位置,配合staticFieldOffset方法使用。傳回值是靜态屬性所在的Class對象的一個記憶體快照
//例:unsafe.getObject(unsafe.staticFieldBase(name), unsafe.staticFieldOffset(name))
public native Object staticFieldBase(Field arg0);

//傳回數組中第一個元素實際位址相對整個數組對象的位址的偏移量
public native int arrayBaseOffset(Class<?> var1);

//傳回數組中第一個元素所占用的記憶體空間
public native int arrayIndexScale(Class<?> var1);
           

3.3 對象操作

類似2.2volatile資料操作,僅是沒有volatile特性,同樣有18個方法,僅展示int類型的:

//獲得給定位址上的int值
public native int getInt(long address);
//設定給定位址上的int值
public native void putInt(long address, int x);
           

3.4 線程控制

//取消阻塞線程
public native void unpark(Object thread);
//阻塞線程
public native void park(boolean isAbsolute, long time);
           

3.5 Class操作

//檢測給定的類是否需要初始化。使用在擷取一個類的靜态屬性的時候(因為一個類如果沒初始化,它的靜态屬性也不會初始化)
public native boolean shouldBeInitialized(Class<?> arg0);

//檢測給定的類是否已經初始化,使用場景同上
public native void ensureClassInitialized(Class<?> arg0);

//定義一個類,傳回類執行個體,此方法會跳過JVM的所有安全檢查。
public native Class<?> defineClass(String name, byte[] b, int off, int len,ClassLoader loader,ProtectionDomain protectionDomain);

//定義一個匿名類,可用于動态建立類
public native Class<?> defineAnonymousClass(Class<?> hostClass, byte[] data, Object[] cpPatches);

//建立一個類的執行個體,但不會調用這個執行個體的構造方法,如果這個類還未被初始化,則初始化這個類
public native Object allocateInstance(Class<?> var1) throws InstantiationException;
           

4 實作自定義原子類

直接看代碼:

public class CASTest {

    public static CountDownLatch latch;

    public static void main(String[] args) throws Exception {
        CASInteger CASInteger = new CASInteger(0);
        Runnable casAdd = () -> {
            //每個線程修改1萬次
            for(int i = 0; i < 10000; i++){
                CASInteger.casIncreament();
            }
            latch.countDown();
        };
        //線程數量100個
        int threadNum = 100;
        latch = new CountDownLatch(threadNum);
        long start = System.currentTimeMillis();
        //啟動所有線程
        for(int i = 0; i < threadNum; i++){
            new Thread(casAdd).start();
        }
        //等待所有線程結束,
        latch.await();
        System.out.println("time cost : " + (System.currentTimeMillis() - start)+" ms");
        System.out.println("result: " + CASInteger.getValue());
    }
}

/**
 * 自定義CAS-Integer,Thread Safe
 */
class CASInteger {
    private sun.misc.Unsafe unsafe;
    private long offset;
    private volatile int value ;

    public CASInteger(int value) throws Exception {
        this.value = value;
        //擷取Unsafe對象
        unsafe = getUnsafeByContructor();
        //目前對象的偏移量
        offset = unsafe.objectFieldOffset(CASInteger.class.getDeclaredField("value"));
    }

    /**
     * 安全自增,僅當更新值比目前對象值多1時,才能更新成功。
     */
    public void casIncreament(){
        //效果等價于:unsafe.getAndAddInt(this, offset, 1);
        while (!unsafe.compareAndSwapInt(this, offset, value, value + 1)){
        }
    }

    public int getValue() {
        return value;
    }

    /**
     * 通過私有構造函數Unsafe()擷取Unsafe
     *
     * @return
     * @throws Exception
     */
    private static Unsafe getUnsafeByContructor() throws Exception {
        // 擷取私有無參構造函數Unsafe()
        Constructor<Unsafe> constructor = Unsafe.class.getDeclaredConstructor();
        // 關閉安全檢查,不代表一定可以通路,但一定可以減小安全檢查的資源消耗,常用于反射類功能中
        constructor.setAccessible(true);
        Unsafe unsafe = (Unsafe) constructor.newInstance();
        return unsafe;
    }
}
           

結果:146毫秒,自增100萬次,資料正确

time cost : 113 ms
result: 1000000
           

PS:value類型是volatile的int類型,為了保證資料可見;compareAndSwapInt傳參必須是int,不能是Integer,不然可能會記憶體奔潰哦。

愛家人,愛生活,愛設計,愛程式設計,擁抱精彩人生!