Java中的Unsafe

2021-02-14 開發小張

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 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);

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.park(false, 0L);     setBlocker(t, null); }
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:保證在這個屏障之前的所有讀寫操作都已經完成。

相關焦點

  • Unsafe函數是什麼?
    不過儘管如此,JVM 還是開了一個後門,JDK 中有一個類 Unsafe,底層是使用C/C++寫的,它提供了硬體級別的原子操作。Unsafe為我們提供了訪問底層的機制,這種機制僅供java核心類庫使用,而不應該被普通用戶使用。
  • Go語言中 Unsafe 的用法
    - 後端早讀課翻譯計劃 第三篇- 本文列舉在 Golang 中 unsafe 的一般用法,以及提供給開發者的一些建議用法。
  • 【java基礎】並發包unsafe類概述
    可以做一個實驗,因為Java源碼中的類,除擴展包都是由Boot ClassLoader加載的,如果我們new一個Object對象,查看Object對象的ClassLoader,它一定是null。,如果需要使用它,則需要利用反射:private static Unsafe getUnsafe(){ try { Field field = Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); Unsafe unsafe
  • 阿里面試官:你了解Java Unsafe類嗎?
    但我們不能通過像Unsafe unsafe = new Unsafe()這種簡單的方式來實現Unsafe的實例化,這是由於Unsafe的構造方法是私有的。Unsafe有一個靜態的getUnsafe()方法,但是如果天真的以為調用該方法就可以的話,那你將遇到一個SecurityException異常,這是由於該方法只能在被信任的代碼中調用。
  • 簡述Unsafe使用
    在程序中過度、不正確使用Unsafe類會使得程序出錯的概率變大,使得Java這種安全的語言變得不再「安全」,因此對Unsafe的使用一定要慎重。使用Unsafe申請的內存是對外內存。= new Num(); System.err.println("new關鍵字創建: n=" + num1.getN()); Num num2 = Num.class.newInstance(); System.err.println("反射創建: n=" + num2.getN()); Unsafe unsafe
  • 阿里面試官:你了解Java Unsafe類嗎?|java|字符串|調用|實例化|...
    但我們不能通過像Unsafe unsafe = new Unsafe()這種簡單的方式來實現Unsafe的實例化,這是由於Unsafe的構造方法是私有的。Unsafe有一個靜態的getUnsafe()方法,但是如果天真的以為調用該方法就可以的話,那你將遇到一個SecurityException異常,這是由於該方法只能在被信任的代碼中調用。
  • Costs of Unsafe Abortions
    A new study looked at the global cost of unsafe abortions, including the many women who are treated for complications of botched procedures.
  • C++、java 和 C 的區別
    一、基礎類型c++:** java:** C#:1.以java為準,c++裡面的int short long 像這樣的整型 一般都有unsigned 和signed2.java和c#裡面都有字符串型 和byte型, 但c++裡面沒有,但它是以另外的形式存儲這類型的數據的,比如 java和c#裡面的 byte其實就是unsigned char類型;c++中字符數組就能存儲字符串 (char a[]={"hello"}; ps:注意c++裡面定義數組 變量必須在中括號前面)。
  • 深度解密Go語言之unsafe
    Offsetof 返回結構體成員在內存中的位置離結構體起始處的字節數,所傳參數必須是結構體的成員。Alignof 返回 m,m 是指當類型進行內存對齊時,它分配到的內存地址能整除 m。注意到以上三個函數返回的結果都是 uintptr 類型,這和 unsafe.Pointer 可以相互轉換。
  • Java魔法類 Unsafe 應用解析
    java -Xbootclasspath/a: ${path}   // 其中path為調用Unsafe相關方法的類所在jar包路徑 其二,通過反射獲取單例對象theUnsafe。典型應用CAS在java.util.concurrent.atomic相關類、Java AQS、CurrentHashMap等實現上有非常廣泛的應用。
  • java並發編程-原子類
    而 java.util.concurrent.atomic 下的類,就是具有原子性的類,可以原子性地執行添加、遞增、遞減等操作。比如之前多線程下的線程不安全的 i++ 問題,到了原子類這裡,就可以用功能相同且線程安全的 getAndIncrement 方法來優雅地解決。
  • java中一半是天使一半是魔鬼的Unsafe類詳解 - 愚公要移山1
    可能我們會奇怪,java中竟然給一個類起名字叫做「不安全」。慢慢看,你就會發現這個類的神奇之處,雖然功能很強大,但是的確不那麼安全。一、簡單介紹首先在Oracle的Jdk8無法獲取到sun.misc包的源碼,想看此包的源碼可以直接下載openjdk。
  • 【厚積薄發】開啟Allow unsafe code的影響
    UWA 問答社區:answer.uwa4d.comUWA QQ群2:793972859(原群已滿員)本期目錄:Q:最近項目引進幾個插件,以及學習別人的插件,代碼中用到了unsafe關鍵字,需要再project settings中勾選Allow unsafe
  • 面試官:說說Java中的信號量?Semaphore
    位於java.util.concurrent報下,一般簡稱juc。一個計數信號量。在概念上,信號量維持一組許可證,如果有必要,每個acquire()都會阻塞,直到許可證可用,然後才能使用它。每個release()添加許可證。應用場景晚上我們去願者上鉤吃烤魚,可以看到路外面站著一排排的人,都在做著等叫號,實際上也就是獲取許可。
  • 硬核解讀:為什麼Rust的Unsafe關鍵字有效?
    就像生活中許多偉大的事情一樣,本文也是源於刁難。Reddit,更確切地說是 r/programming,已經讓我抓狂了。所以我的目標很簡單:我想概要的介紹一下為什麼 Rust 的 unsafe 關鍵字有效,而在 C/C++ 中類似的方法卻行不通。 本文最初發布於 jam1garner 的個人博客,由 InfoQ 中文站翻譯並分享。
  • java中包名不能以java開頭
    java中自己寫的類的包名為什麼不能以java開頭?這是因為jvm在加載類的時候,連接階段,會做安全校驗,包名startsWith("java.")在運行期會報錯。具體是在ClassLoader.java中的preDefineClass方法:if ((name != null) && name.startsWith("java."))
  • 詳解java並發原子類AtomicInteger(基於jdk1.8源碼分析)
    java並發包裡面的類一直是學習和面試的重點,這篇文章主要是對java並發包的其中一個類AtomicInteger的講解。從為什麼要出現AtomicInteger再到其底層原理來一個分析。一、從a++說起為什麼使用AtomicInteger我們知道java並發機制中主要有三個特性需要我們去考慮,原子性、可見性和有序性。
  • Java認證:Java編程中實現中文排序
    Java認證:Java編程中實現中文排序  第一種情況:  Comparator cmp = Collator.getInstance(java.util.Locale.CHINA);  String[] arr = { 「張三」, 「李四」, 「王五」, 「劉六」 };
  • Java中 休眠(sleep)
    java中解析字符串為時間java中定義日期格式的轉換符java中如重複提供日期java中日期和時間的格式化編碼java中使用printf格式化獲取當前日期
  • Java編程中常見的異常
    java.lang.arithmeticexception     這個異常的解釋是"數學運算異常",比如程序中出現了除以零這樣的運算就會出這樣的異常,對這種異常,大家就要好好檢查一下自己程序中涉及到數學運算的地方,公式是不是有不妥了。 4.