Java中的Unsafe

Java和C++語言的一個重要區別就是Java中咱們沒法直接操做一塊內存區域,不能像C++中那樣能夠本身申請內存和釋放內存。Java中的Unsafe類爲咱們提供了相似C++手動管理內存的能力。 Unsafe類,全限定名是sun.misc.Unsafe,從名字中咱們能夠看出來這個類對普通程序員來講是「危險」的,通常應用開發者不會用到這個類。程序員

Unsafe類是"final"的,不容許繼承。且構造函數是private的:數組

public final class Unsafe {
    private static final Unsafe theUnsafe;
    public static final int INVALID_FIELD_OFFSET = -1;

    private static native void registerNatives();
    // 構造函數是private的,不容許外部實例化
    private Unsafe() {
    }
    ...
}
複製代碼

所以咱們沒法在外部對Unsafe進行實例化。bash

獲取Unsafe

Unsafe沒法實例化,那麼怎麼獲取Unsafe呢?答案就是經過反射來獲取Unsafe:函數

public Unsafe getUnsafe() throws IllegalAccessException {
    Field unsafeField = Unsafe.class.getDeclaredFields()[0];
    unsafeField.setAccessible(true);
    Unsafe unsafe = (Unsafe) unsafeField.get(null);
    return unsafe;
}
複製代碼

主要功能

Unsafe的功能以下圖:ui

Unsafe-xmind

普通讀寫

經過Unsafe能夠讀寫一個類的屬性,即便這個屬性是私有的,也能夠對這個屬性進行讀寫。spa

讀寫一個Object屬性的相關方法線程

public native int getInt(Object var1, long var2);

public native void putInt(Object var1, long var2, int var4);
複製代碼

getInt用於從對象的指定偏移地址處讀取一個int。putInt用於在對象指定偏移地址處寫入一個int。其餘的primitive type也有對應的方法。code

Unsafe還能夠直接在一個地址上讀寫cdn

public native byte getByte(long var1);

public native void putByte(long var1, byte var3);
複製代碼

getByte用於從指定內存地址處開始讀取一個byte。putByte用於從指定內存地址寫入一個byte。其餘的primitive type也有對應的方法。對象

volatile讀寫

普通的讀寫沒法保證可見性和有序性,而volatile讀寫就能夠保證可見性和有序性。

public native int getIntVolatile(Object var1, long var2);

public native void putIntVolatile(Object var1, long var2, int var4);
複製代碼

getIntVolatile方法用於在對象指定偏移地址處volatile讀取一個int。putIntVolatile方法用於在對象指定偏移地址處volatile寫入一個int。

volatile讀寫相對普通讀寫是更加昂貴的,由於須要保證可見性和有序性,而與volatile寫入相比putOrderedXX寫入代價相對較低,putOrderedXX寫入不保證可見性,可是保證有序性,所謂有序性,就是保證指令不會重排序。

有序寫入

有序寫入只保證寫入的有序性,不保證可見性,就是說一個線程的寫入不保證其餘線程立馬可見。

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);
複製代碼

直接內存操做

咱們都知道Java不能夠直接對內存進行操做,對象內存的分配和回收都是由JVM幫助咱們實現的。可是Unsafe爲咱們在Java中提供了直接操做內存的能力。

// 分配內存
public native long allocateMemory(long var1);
// 從新分配內存
public native long reallocateMemory(long var1, long var3);
// 內存初始化
public native void setMemory(long var1, long var3, byte var5);
// 內存複製
public native void copyMemory(Object var1, long var2, Object var4, long var5, long var7);
// 清除內存
public native void freeMemory(long var1);
複製代碼

CAS相關

JUC中大量運用了CAS操做,能夠說CAS操做是JUC的基礎,所以CAS操做是很是重要的。Unsafe中提供了int,long和Object的CAS操做:

public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
複製代碼

CAS通常用於樂觀鎖,它在Java中有普遍的應用,ConcurrentHashMap,ConcurrentLinkedQueue中都有用到CAS來實現樂觀鎖。

偏移量相關

public native long staticFieldOffset(Field var1);

public native long objectFieldOffset(Field var1);

public native Object staticFieldBase(Field var1);

public native int arrayBaseOffset(Class<?> var1);

public native int arrayIndexScale(Class<?> var1);
複製代碼

staticFieldOffset方法用於獲取靜態屬性Field在對象中的偏移量,讀寫靜態屬性時必須獲取其偏移量。objectFieldOffset方法用於獲取非靜態屬性Field在對象實例中的偏移量,讀寫對象的非靜態屬性時會用到這個偏移量。staticFieldBase方法用於返回Field所在的對象。arrayBaseOffset方法用於返回數組中第一個元素實際地址相對整個數組對象的地址的偏移量。arrayIndexScale方法用於計算數組中第一個元素所佔用的內存空間。

線程調度

public native void unpark(Object var1);

public native void park(boolean var1, long var2);

public native void monitorEnter(Object var1);

public native void monitorExit(Object var1);

public native boolean tryMonitorEnter(Object var1);
複製代碼

park方法和unpark方法相信看過LockSupport類的都不會陌生,這兩個方法主要用來掛起和喚醒線程。LockSupport中的park和unpark方法正是經過Unsafe來實現的:

// 掛起線程
public static void park(Object blocker) {
    Thread t = Thread.currentThread();
    setBlocker(t, blocker); // 經過Unsafe的putObject方法設置阻塞阻塞當前線程的blocker
    UNSAFE.park(false, 0L); // 經過Unsafe的park方法來阻塞當前線程,注意此方法將當前線程阻塞後,當前線程就不會繼續往下走了,直到其餘線程unpark此線程
    setBlocker(t, null); // 清除blocker
}

// 喚醒線程
public static void unpark(Thread thread) {
    if (thread != null)
        UNSAFE.unpark(thread);
}
複製代碼

monitorEnter方法和monitorExit方法用於加鎖,Java中的synchronized鎖就是經過這兩個指令來實現的。

類加載

public native Class<?> defineClass(String var1, byte[] var2, int var3, int var4, ClassLoader var5, ProtectionDomain var6);

public native Class<?> defineAnonymousClass(Class<?> var1, byte[] var2, Object[] var3);

public native Object allocateInstance(Class<?> var1) throws InstantiationException;

public native boolean shouldBeInitialized(Class<?> var1);

public native void ensureClassInitialized(Class<?> var1);
複製代碼

defineClass方法定義一個類,用於動態地建立類。 defineAnonymousClass用於動態的建立一個匿名內部類。 allocateInstance方法用於建立一個類的實例,可是不會調用這個實例的構造方法,若是這個類還未被初始化,則初始化這個類。 shouldBeInitialized方法用於判斷是否須要初始化一個類。 ensureClassInitialized方法用於保證已經初始化過一個類。

內存屏障

public native void loadFence();

public native void storeFence();

public native void fullFence();
複製代碼

loadFence:保證在這個屏障以前的全部讀操做都已經完成。 storeFence:保證在這個屏障以前的全部寫操做都已經完成。 fullFence:保證在這個屏障以前的全部讀寫操做都已經完成。

相關文章
相關標籤/搜索