前言
我們經常在JUC包下的ConcurrentHashMap、Atomic開頭的原子操作類、AQS以及LockSupport裏面看到Unsafe類的身影,這個Unsafe類究竟是幹什麼的,本文可以帶着讀者一探究竟。
Java和C++、C語言的一個重要區別,就是Java中我們無法直接操作一塊內存區域,而在C++、C中卻可以自己申請內存和釋放內存。Unsafe類的設計,爲我們提供了手動管理內存的能力。
如同它的名字一樣,它被認定爲不安全的。直接操縱內存,意味着實例化出來的對象不會受到JVM的管理,不會被GC,需要手動進行回收,容易出現內存泄露的問題。因此,官方並不建議我們在自己的應用程序中使用該類。
構造方法
public final class Unsafe {
private static final Unsafe theUnsafe;
private Unsafe() {
}
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
//其他方法
}
可以看得出來,該類被final修飾,不允許被繼承。構造方法是私有的,在外部不可被實例化。(關於final更多的作用,可以移步這篇文章關鍵詞final的作用)
但在內部提供了一個獲取單例的getUnsafe()方法,不過該方法做了限制。如果是普通調用的話,它會拋出一個SecurityException異常。只有由系統類加載器(BootStrap classLoader)加載的類纔可以調用這個類中的方法。
如果var0由系統類加載器加載的話,那麼var0.getClassLoader()會返回null,VM.isSystemDomainLoader(null)則直接返回true,此時便不會拋出SecurityException異常。
當然,也不是無法獲取到Unsafe類的實例,我們在文章最後會通過反射來獲取。
獲取偏移量
public native long staticFieldOffset(Field var1);
public native long objectFieldOffset(Field var1);
- staticFieldOffset用於獲取某一個靜態屬性在對象地址中的偏移量
- objectFieldOffset用於獲取某一個非靜態屬性在對象實例地址中的偏移量
偏移量這個名詞在Unsafe類中十分重要,該類中80%的方法都需要依賴這個偏移量。
分配、釋放內存等
//分配內存
public native long allocateMemory(long var1);
//擴展或重新分配內存
public native long reallocateMemory(long var1, long var3);
//內存初始化
public native void setMemory(Object var1, long var2, long var4, byte var6);
//內存複製
public native void copyMemory(Object var1, long var2, Object var4, long var5, long var7);
//釋放內存
public native void freeMemory(long var1);
//創建對象實例並返回該實例
public native Object allocateInstance(Class<?> var1) throws InstantiationException;
其中allocateMemory()、reallocateMemory()、freeMemory()分別用於分配內存,擴展或重新分配內存和釋放內存,與C語言中 malloc()、realloc()、free()對應。
allocateInstance()方法會通過Class對象創建一個類的實例,且不需要調用其構造函數、初始化代碼、JVM安全檢查等等。
普通讀寫
public native int getInt(long var1);
public native void putInt(long var1, int var3);
- getInt() 在指定內存地址var1處讀取一個int
- putInt() 在指定內存地址var1處寫入一個新int類型的數據var3
普通的讀寫,無法保證有序性與可見性。關於可見性,可以先移步到我的另外一篇文章多線程之內存可見性
volatile讀寫
public native int getIntVolatile(Object var1, long var2);
public native void putIntVolatile(Object var1, long var2, int var4);
- getIntVolatile 在對象var1的指定偏移處var2讀取一個int
- putIntVolatie 在對象var1的指定偏移處var2寫入一個int類型的數據var4
volatie能夠保證有序性以及可見性,volatie保證有序性的一個實例,可以參考我的另外一篇文章淺說Synchronized中使用synchronized與volatie實現單例模式中雙重檢驗鎖的部分。
CAS操作
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
以compareAndSwapInt爲例,在對象var1指定偏移量var2處讀取一個int值real,如果var4=real,則用var5更新這個real,並返回true,否則返回false。
CAS用於實現樂觀鎖,每次更新時,都假設不會有其他線程併發修改,而只是在修改的時候判斷該值是否被修改過。如果符合預期的值,則直接更新它,否則進入忙循環中,一直判斷是否符合期望。在循環次數少便可以直接更新值的情況下,CAS機制比悲觀鎖擁有更好的性能。當然,如果循環次數過多,也是會白白浪費CPU資源。
synchronized就是一中悲觀鎖的實現,關於synchronized原理,可以移步我的另外一篇文章Synchronized的優化
關於CAS機制的詳細介紹,我會另開篇幅。
線程調度
public native void park(boolean var1, long var2);
public native void unpark(Object var1);
- park() 用於阻塞當前線程,如果var1=true,則var2的單位爲毫秒,否則爲納秒。
- unpark() 用於恢復一個之前被park的線程var1
LockSupport裏面的park()與unpark()方法內部正是通過以上兩個本地方法來實現的。
內存屏障
public native void loadFence();
public native void storeFence();
public native void fullFence();
- loadFence 保證在這個屏障之前,所有的讀操作全部完成。這裏的load對應於從局部變量表中讀取某個位置上的元素到棧頂
- storeFence 保證在這個屏障之前,所有的寫操作全部完成。這裏的store對應於從棧頂出棧某個元素保存到局部變量表的某個位置上
- fullFence 保證在這個屏障之前,所有的讀寫操作全部完成,相當於load+store
反射獲取Unsafe實例
package com.yang.testUnsafe;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class Main {
static class Student {
private String name;
private int age;
public Student() {
System.out.println("通過構造方法");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException {
//1.獲取Unsafe類的實例
//但無法通過Unsafe.getUnsafe(),由於Main類不是系統類加載器加載,因此會拋出SecurityException異常
//Unsafe unsafe = Unsafe.getUnsafe();
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafe.get(null);
//2.爲Student對象分配內存
//該方法不需要調用構造方法
Student student = (Student) unsafe.allocateInstance(Student.class);
//3.獲取student實例中age屬性的偏移量
Field age = student.getClass().getDeclaredField("age");
long ageOffset = unsafe.objectFieldOffset(age);
//4.利用CAS操作,當age=0時,將age變爲1
boolean casResult = unsafe.compareAndSwapInt(student, ageOffset, 0, 1);
System.out.println(casResult);//輸出true
System.out.println(student.getAge());//輸出1
}
}
總結
使用Unsafe類直接操縱內存,意味着速度更快,效率更高,但也更加危險。之前盛傳Unsafe類將在Java9中移除,一時間風波四起,具體的文章可以參考這篇Java 9中將移除 Sun.misc.Unsafe(譯)。
但其實Java9出現之後,只是對其進行了改進和優化,不過依然是不推薦開發者使用Unsafe類。