一半是天使一半是魔鬼的Unsafe類詳解

可能咱們會奇怪,java中居然給一個類起名字叫作「不安全」。慢慢看,你就會發現這個類的神奇之處,雖然功能很強大,可是的確不那麼安全。java

1、簡單介紹windows

首先在Oracle的Jdk8沒法獲取到sun.misc包的源碼,想看此包的源碼能夠直接下載openjdk。數組

一、預備工做安全

openjdk的源碼我下載的是openjdk-8u40-src-b25-10_feb_2015,有須要的能夠私信我,若是是我公衆號粉絲,我會直接附加上這個百度雲資源。在下載完成以後,而後就能夠直接導入咱們的eclipse了。併發

windows->preference->installed jres->選中jre->edit->rt.jar->source attachment->external folders->openjdk源碼路徑。此時就能夠查看咱們的Unsafe類的源碼了。框架

二、簡介說明eclipse

若是你學習了一些java併發包裏面的類源碼的話,對這個Unsafe類必定不陌生,整個java併發包底層實現的核心就是它,在好久以前盛傳着這個類將要在jdk9移除,實時上若是移除了那麼一大批框架將會消失,好比說赫赫有名的Netty框架。最終jdk9出現的時候也只是對其進行了改進和優化。不過這也再一次說明了這個類的重要地位。jvm

爲何說它一半是天使一半是魔鬼呢?要回答這個問題,咱們仍是要從其特性來解釋。高併發

Unsafe類使Java擁有了像C語言的指針同樣操做內存空間的能力,一旦可以直接操做內存,這也就意味着(1)不受jvm管理,也就意味着沒法被GC,須要咱們手動GC,稍有不慎就會出現內存泄漏。源碼分析

(2)Unsafe的很多方法中必須提供原始地址(內存地址)和被替換對象的地址,偏移量要本身計算,一旦出現問題就是JVM崩潰級別的異常,會致使整個JVM實例崩潰,表現爲應用程序直接crash掉。

(3)直接操做內存,也意味着其速度更快,在高併發的條件之下可以很好地提升效率。

所以,從上面三個角度來看,雖然在必定程度上提高了效率可是也帶來了指針的不安全性。

下面咱們深刻到源碼中看看,提供了什麼方法直接操做內存。

2、源碼分析

Unsafe中一共有82個public native修飾的方法,還有幾十個基於這82個public native方法的其餘方法。這些方法大致能夠歸結爲如下幾類:

(1)初始化操做

(2)操做對象屬性

(3)操做數組元素

(4)內存管理

(5)內存屏障

(6)線程掛起和回覆

(7)CAS機制

下面咱們對這些方法盡進行一個大體的分析。

一、初始化

//一、註冊native方法,是的Unsafe類能夠操做C語言
	private static native void registerNatives();
    static {
        registerNatives();
        sun.reflect.Reflection.registerMethodsToFilter(Unsafe.class, "getUnsafe");
    }
	//二、構造方法
    private Unsafe() {}
	//三、初始化方法
    private static final Unsafe theUnsafe = new Unsafe();
	//四、初始化方法實現
    @CallerSensitive
    public static Unsafe getUnsafe() {
        Class<?> caller = Reflection.getCallerClass();
        if (!VM.isSystemDomainLoader(caller.getClassLoader()))
            throw new SecurityException("Unsafe");
        return theUnsafe;
    }
複製代碼

在這裏咱們看到Unsafe的初始化方法主要是經過getUnsafe方法的單例模式實現的,調用JVM本地方法registerNatives()和sun.reflect.Reflection,經過Reflection的getCallerClass判斷當前調用的類是不是主類加載器(BootStrap classLoader)加載的,不然的話拋出一個SecurityException。這也證實了一個問題,那就是隻有由主類加載器(BootStrap classLoader)加載的類才能調用這個類中的方法。

二、操做屬性方法

(1)public native Object getObject(Object o, long offset);

經過給定的Java變量獲取引用值。這裏其實是獲取一個Java對象o中,獲取偏移地址爲offset的屬性的值,此方法能夠突破修飾符的抑制,也就是無視private、protected和default修飾符。相似的方法有getInt、getDouble等等。同理還有putObject方法。

(2)public native Object getObjectVolatile(Object o, long offset);

強制從主存中獲取屬性值。相似的方法有getIntVolatile、getDoubleVolatile等等。同理還有putObjectVolatile。

(3)public native void putOrderedObject(Object o, long offset, Object x);

設置o對象中offset偏移地址offset對應的Object型field的值爲指定值x。這是一個有序或者有延遲的putObjectVolatile方法,而且不保證值的改變被其餘線程當即看到。只有在field被volatile修飾而且指望被修改的時候使用纔會生效。相似的方法有putOrderedInt和putOrderedLong。

(4)public native long staticFieldOffset(Field f);

返回給定的靜態屬性在它的類的存儲分配中的位置(偏移地址)。

(5)public native long objectFieldOffset(Field f);

返回給定的非靜態屬性在它的類的存儲分配中的位置(偏移地址)。

(6)public native Object staticFieldBase(Field f);

返回給定的靜態屬性的位置,配合staticFieldOffset方法使用。

三、操做數組

(1)public native int arrayBaseOffset(Class<?> arrayClass);

返回數組類型的第一個元素的偏移地址(基礎偏移地址)。

(2)public native int arrayIndexScale(Class<?> arrayClass);

返回數組中元素與元素之間的偏移地址的增量。

這兩個方法配合使用就能夠定位到任何一個元素的地址。

四、內存管理

(1)public native int addressSize();

獲取本地指針的大小(單位是byte),一般值爲4或者8。常量ADDRESS_SIZE就是調用此方法。

(2)public native int pageSize();

獲取本地內存的頁數,此值爲2的冪次方。

(3)public native long allocateMemory(long bytes);

分配一塊新的本地內存,經過bytes指定內存塊的大小(單位是byte),返回新開闢的內存的地址。

(4)public native long reallocateMemory(long address, long bytes);

經過指定的內存地址address從新調整本地內存塊的大小,調整後的內存塊大小經過bytes指定(單位爲byte)。

(5)public native void setMemory(Object o, long offset, long bytes, byte value);

將給定內存塊中的全部字節設置爲固定值(一般是0)。

五、線程掛起和恢復

(1)public native void unpark(Object thread);

釋放被park建立的在一個線程上的阻塞。因爲其不安全性,所以必須保證線程是存活的。

(2)public native void park(boolean isAbsolute, long time);`

阻塞當前線程,一直等道unpark方法被調用。

六、內存屏障

(1)public native void loadFence();

在該方法以前的全部讀操做,必定在load屏障以前執行完成。

(2)public native void storeFence();

在該方法以前的全部寫操做,必定在store屏障以前執行完成

(3)public native void fullFence();

在該方法以前的全部讀寫操做,必定在full屏障以前執行完成,這個內存屏障至關於上面兩個(load屏障和store屏障)的合體功能。

七、CAS機制

public final native boolean compareAndSwapObject( Object o, long offset, Object expected, Object x);
    public final native boolean compareAndSwapInt( Object o, long offset,int expected, int x);
    public final native boolean compareAndSwapLong( Object o, long offset, long expected,long x);
複製代碼

這個Unsafe類實際上是貫穿到整個java併發包體系中的,不論是你看原子包仍是lock包底部都有這樣的一個類,咱們須要記住的不是每個方法,而是上面七類的標題。也就是具體有什麼功能。

3、使用

說了這麼久的源碼在這裏才介紹其使用,是由於官方並不推薦咱們使用,也就是說咱們沒法直接new出來一個Unsafe類出來,那咱們該如何使用呢?在好久以前我曾寫過一篇介紹java反射機制的文章,沒錯就是這個反射機制,牛的不行。Unsafe就能夠經過反射機制來獲取。

public class UnsafeTest {
	public static void main(String[] args) throws Exception {
		//這裏的theUnsafe就是咱們源碼中的那個theUnsafe
        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        Unsafe unsafe = (Unsafe) theUnsafe.get(null);
        
        //一、建立對象實例
        Author author = (Author) unsafe.allocateInstance(Author.class);
        //二、操做對象的屬性
        Field ageField = Author.class.getDeclaredField("age");
        long fieldOffset = unsafe.objectFieldOffset(ageField);
        //三、操做數組
        String[] strings = new String[]{"1", "2", "3"};
        long i = unsafe.arrayBaseOffset(String[].class);
        //四、操做內存
        long address = unsafe.allocateMemory(8L);        
    }
}
複製代碼

在這裏只是給出一些簡單的例子,其用法能夠參照源碼分析中那七個方向。

仔細閱讀jdk的源碼有時候真的能夠收穫一些意想不到的東西。感謝你們支持。

相關文章
相關標籤/搜索