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進行實例化。
獲取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的功能如下圖:
普通讀寫
通過Unsafe可以讀寫一個類的屬性,即使這個屬性是私有的,也可以對這個屬性進行讀寫。
讀寫一個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也有對應的方法。
Unsafe還可以直接在一個地址上讀寫
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);
堆外內存:存在於JVM管控之外的內存區域,Java中對於其的操作依賴於Unsafe
使用堆外內存的原因:
對垃圾回收停頓的改善。
- 提升程序I/O操作的性能。
典型應用:DirectByteBuffer
CAS相關
CAS全程是Compare And Swap,即比較交換;其核心算法:執行函數CAS(V,E,N)
其中:
V代表要更新的變量
E代表預期值
N代表新值。
算法:當V==E時,修改V=N;否則什么都不做。若V!=E即代表此變量在其他線程中進行了更新
JUC中大量運用了CAS操作,可以說CAS操作是JUC的基礎,因此CAS操作是非常重要的。Unsafe中提供了int,long和Object的CAS操作:
CAS一般用於樂觀鎖,它在Java中有廣泛的應用,ConcurrentHashMap,ConcurrentLinkedQueue中都有用到CAS來實現樂觀鎖。
/** CAS @param o 包含要修改field的對象 @param offset 對象中某field的偏移量 @param expected 期望值 @param update 更新值 @return true | false 執行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);
典型應用:java.util.concurrent.atomic相關類、Java AQS、CurrentHashMap等
偏移量相關
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 thread); // 阻塞線程 public native void park(boolean isAbsolute, long time); // 獲得對象鎖(可重入鎖) /** @deprecated */ @Deprecated public native void monitorEnter(Object var1); // 釋放對象鎖 /** @deprecated */ @Deprecated public native void monitorExit(Object var1); //嘗試獲取對象鎖 @Deprecated public native boolean tryMonitorEnter(Object o);
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 long staticFieldOffset(Field f); //獲取一個靜態類中給定字段的對象指針 public native Object staticFieldBase(Field f); //判斷是否需要初始化一個類,通常在獲取一個類的靜態屬性的時候(因為一個類如果沒初始化,它的靜態屬性也不會初始化)使用。 當且僅當ensureClassInitialized方法不生效時返回false。 public native boolean shouldBeInitialized(Class<?> c); //檢測給定的類是否已經初始化。通常在獲取一個類的靜態屬性的時候(因為一個類如果沒初始化,它的靜態屬性也不會初始化)使用。 public native void ensureClassInitialized(Class<?> c); //定義一個類,此方法會跳過JVM的所有安全檢查,默認情況下,ClassLoader(類加載器)和ProtectionDomain(保護域)實例來源於調用者 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);
defineClass方法定義一個類,用於動態地創建類。
defineAnonymousClass用於動態的創建一個匿名內部類。
allocateInstance方法用於創建一個類的實例,但是不會調用這個實例的構造方法,如果這個類還未被初始化,則初始化這個類。
shouldBeInitialized方法用於判斷是否需要初始化一個類。
ensureClassInitialized方法用於保證已經初始化過一個類。
內存屏障
public native void loadFence(); public native void storeFence(); public native void fullFence();
loadFence:保證在這個屏障之前的所有讀操作都已經完成。
storeFence:保證在這個屏障之前的所有寫操作都已經完成。
fullFence:保證在這個屏障之前的所有讀寫操作都已經完成