1. 程式人生 > >Java魔法類:sun.misc.Unsafe

Java魔法類:sun.misc.Unsafe

Unsafe類在jdk 原始碼的多個類中用到,這個類的提供了一些繞開JVM的更底層功能,基於它的實現可以提高效率。但是,它是一把雙刃劍:正如它的名字所預示的那樣,它是Unsafe的,它所分配的記憶體需要手動free(不被GC回收)。Unsafe類,提供了JNI某些功能的簡單替代:確保高效性的同時,使事情變得更簡單。

這篇文章主要是以下文章的整理、翻譯。

http://mishadoff.com/blog/java-magic-part-4-sun-dot-misc-dot-unsafe/

1. Unsafe API的大部分方法都是native實現,它由105個方法組成,主要包括以下幾類:

(1)Info相關。主要返回某些低級別的記憶體資訊:addressSize(), pageSize()

(2)Objects相關。主要提供Object和它的域操縱方法:allocateInstance(),objectFieldOffset()

(3)Class相關。主要提供Class和它的靜態域操縱方法:staticFieldOffset(),defineClass(),defineAnonymousClass(),ensureClassInitialized()

(4)Arrays相關。陣列操縱方法:arrayBaseOffset(),arrayIndexScale()

(5)Synchronization相關。主要提供低級別同步原語(如基於CPU的CAS(Compare-And-Swap)原語):monitorEnter(),tryMonitorEnter(),monitorExit(),compareAndSwapInt(),putOrderedInt()

(6)Memory相關。直接記憶體訪問方法(繞過JVM堆直接操縱本地記憶體):allocateMemory(),copyMemory(),freeMemory(),getAddress(),getInt(),putInt()

2. Unsafe類例項的獲取

Unsafe類設計只提供給JVM信任的啟動類載入器所使用,是一個典型的單例模式類。它的例項獲取方法如下:

public static Unsafe getUnsafe() {
    Class cc = sun.reflect.Reflection.getCallerClass(2);
    if (cc.getClassLoader
() != null) throw new SecurityException("Unsafe"); return theUnsafe; }

非啟動類載入器直接呼叫Unsafe.getUnsafe()方法會丟擲SecurityException(具體原因涉及JVM類的雙親載入機制)。

解決辦法有兩個,其一是通過JVM引數-Xbootclasspath指定要使用的類為啟動類,另外一個辦法就是java反射了。

Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);

通過將private單例例項暴力設定accessible為true,然後通過Field的get方法,直接獲取一個Object強制轉換為Unsafe。在IDE中,這些方法會被標誌為Error,可以通過以下設定解決:

Preferences -> Java -> Compiler -> Errors/Warnings ->
Deprecated and restricted API -> Forbidden reference -> Warning
3. Unsafe類“有趣”的應用場景

(1)繞過類初始化方法。當你想要繞過物件構造方法、安全檢查器或者沒有public的構造方法時,allocateInstance()方法變得非常有用。

class A {
    private long a; // not initialized value
 
    public A() {
        this.a = 1; // initialization
    }
 
    public long a() { return this.a; }
}

以下是構造方法、反射方法和allocateInstance()的對照

A o1 = new A(); // constructor
o1.a(); // prints 1
 
A o2 = A.class.newInstance(); // reflection
o2.a(); // prints 1
 
A o3 = (A) unsafe.allocateInstance(A.class); // unsafe
o3.a(); // prints 0

allocateInstance()根本沒有進入構造方法,在單例模式時,我們似乎看到了危機。

(2)記憶體修改

記憶體修改在c語言中是比較常見的,在Java中,可以用它繞過安全檢查器。

考慮以下簡單准入檢查規則:

class Guard {
    private int ACCESS_ALLOWED = 1;
 
    public boolean giveAccess() {
        return 42 == ACCESS_ALLOWED;
    }
}

在正常情況下,giveAccess總會返回false,但事情不總是這樣

Guard guard = new Guard();
guard.giveAccess();   // false, no access
 
// bypass
Unsafe unsafe = getUnsafe();
Field f = guard.getClass().getDeclaredField("ACCESS_ALLOWED");
unsafe.putInt(guard, unsafe.objectFieldOffset(f), 42); // memory corruption
 
guard.giveAccess(); // true, access granted

通過計算記憶體偏移,並使用putInt()方法,類的ACCESS_ALLOWED被修改。在已知類結構的時候,資料的偏移總是可以計算出來(與c++中的類中資料的偏移計算是一致的)。

(3)實現類似C語言的sizeOf()函式

通過結合Java反射和objectFieldOffset()函式實現一個C-like sizeOf()函式。

public static long sizeOf(Object o) {
    Unsafe u = getUnsafe();
    HashSet fields = new HashSet();
    Class c = o.getClass();
    while (c != Object.class) {
        for (Field f : c.getDeclaredFields()) {
            if ((f.getModifiers() & Modifier.STATIC) == 0) {
                fields.add(f);
            }
        }
        c = c.getSuperclass();
    }
 
    // get offset
    long maxSize = 0;
    for (Field f : fields) {
        long offset = u.objectFieldOffset(f);
        if (offset > maxSize) {
            maxSize = offset;
        }
    }
 
    return ((maxSize/8) + 1) * 8;   // padding
}

演算法的思路非常清晰:從底層子類開始,依次取出它自己和它的所有超類的非靜態域,放置到一個HashSet中(重複的只計算一次,Java是單繼承),然後使用objectFieldOffset()獲得一個最大偏移,最後還考慮了對齊。

在32位的JVM中,可以通過讀取class檔案偏移為12的long來獲取size。

public static long sizeOf(Object object){
    return getUnsafe().getAddress(
        normalize(getUnsafe().getInt(object, 4L)) + 12L);
}

其中normalize()函式是一個將有符號int轉為無符號long的方法

private static long normalize(int value) {
    if(value >= 0) return value;
    return (0L >>> 32) & value;
}

兩個sizeOf()計算的類的尺寸是一致的。最標準的sizeOf()實現是使用java.lang.instrument,但是,它需要指定命令列引數-javaagent。

(4)實現Java淺複製

標準的淺複製方案是實現Cloneable介面或者自己實現的複製函式,它們都不是多用途的函式。通過結合sizeOf()方法,可以實現淺複製。

static Object shallowCopy(Object obj) {
    long size = sizeOf(obj);
    long start = toAddress(obj);
    long address = getUnsafe().allocateMemory(size);
    getUnsafe().copyMemory(start, address, size);
    return fromAddress(address);
}

以下的toAddress()和fromAddress()分別將物件轉換到它的地址以及相反操作。

static long toAddress(Object obj) {
    Object[] array = new Object[] {obj};
    long baseOffset = getUnsafe().arrayBaseOffset(Object[].class);
    return normalize(getUnsafe().getInt(array, baseOffset));
}
 
static Object fromAddress(long address) {
    Object[] array = new Object[] {null};
    long baseOffset = getUnsafe().arrayBaseOffset(Object[].class);
    getUnsafe().putLong(array, baseOffset, address);
    return array[0];
}

以上的淺複製函式可以應用於任意java物件,它的尺寸是動態計算的。

(5)消去記憶體中的密碼

密碼欄位儲存在String中,但是,String的回收是受到JVM管理的。最安全的做法是,在密碼欄位使用完之後,將它的值覆蓋。

Field stringValue = String.class.getDeclaredField("value");
stringValue.setAccessible(true);
char[] mem = (char[]) stringValue.get(password);
for (int i=0; i < mem.length; i++) {
  mem[i] = '?';
}

(6)動態載入類

標準的動態載入類的方法是Class.forName()(在編寫jdbc程式時,記憶深刻),使用Unsafe也可以動態載入java 的class檔案。

byte[] classContents = getClassContent();
Class c = getUnsafe().defineClass(
              null, classContents, 0, classContents.length);
    c.getMethod("a").invoke(c.newInstance(), null); // 1
getClassContent()方法,將一個class檔案,讀取到一個byte陣列。
 
private static byte[] getClassContent() throws Exception {
    File f = new File("/home/mishadoff/tmp/A.class");
    FileInputStream input = new FileInputStream(f);
    byte[] content = new byte[(int)f.length()];
    input.read(content);
    input.close();
    return content;
}

動態載入、代理、切片等功能中可以應用。

(7)包裝受檢異常為執行時異常。

    
getUnsafe().throwException(new IOException());

當你不希望捕獲受檢異常時,可以這樣做(並不推薦)。

(8)快速序列化

標準的java Serializable速度很慢,它還限制類必須有public無參建構函式。Externalizable好些,它需要為要序列化的類指定模式。流行的高效序列化庫,比如kryo依賴於第三方庫,會增加記憶體的消耗。可以通過getInt(),getLong(),getObject()等方法獲取類中的域的實際值,將類名稱等資訊一起持久化到檔案。kryo有使用Unsafe的嘗試,但是沒有具體的效能提升的資料。(http://code.google.com/p/kryo/issues/detail?id=75)

(9)在非Java堆中分配記憶體

使用java 的new會在堆中為物件分配記憶體,並且物件的生命週期內,會被JVM GC管理。

class SuperArray {
    private final static int BYTE = 1;
 
    private long size;
    private long address;
 
    public SuperArray(long size) {
        this.size = size;
        address = getUnsafe().allocateMemory(size * BYTE);
    }
 
    public void set(long i, byte value) {
        getUnsafe().putByte(address + i * BYTE, value);
    }
 
    public int get(long idx) {
        return getUnsafe().getByte(address + idx * BYTE);
    }
 
    public long size() {
        return size;
    }
}

Unsafe分配的記憶體,不受Integer.MAX_VALUE的限制,並且分配在非堆記憶體,使用它時,需要非常謹慎:忘記手動回收時,會產生記憶體洩露;非法的地址訪問時,會導致JVM崩潰。在需要分配大的連續區域、實時程式設計(不能容忍JVM延遲)時,可以使用它。java.nio使用這一技術。

(10)Java併發中的應用

通過使用Unsafe.compareAndSwap()可以用來實現高效的無鎖資料結構。

class CASCounter implements Counter {
    private volatile long counter = 0;
    private Unsafe unsafe;
    private long offset;

    public CASCounter() throws Exception {
        unsafe = getUnsafe();
        offset = unsafe.objectFieldOffset(CASCounter.class.getDeclaredField("counter"));
    }

    @Override
    public void increment() {
        long before = counter;
        while (!unsafe.compareAndSwapLong(this, offset, before, before + 1)) {
            before = counter;
        }
    }

    @Override
    public long getCounter() {
        return counter;
    }
}

通過測試,以上資料結構與java的原子變數的效率基本一致,Java原子變數也使用Unsafe的compareAndSwap()方法,而這個方法最終會對應到cpu的對應原語,因此,它的效率非常高。這裡有一個實現無鎖HashMap的方案(http://www.azulsystems.com/about_us/presentations/lock-free-hash ,這個方案的思路是:分析各個狀態,建立拷貝,修改拷貝,使用CAS原語,自旋鎖),在普通的伺服器機器(核心<32),使用ConcurrentHashMap(JDK8以前,預設16路分離鎖實現,JDK8中ConcurrentHashMap已經使用無鎖實現)明顯已經夠用。

相關推薦

Java魔法sun.misc.Unsafe

Unsafe類在jdk 原始碼的多個類中用到,這個類的提供了一些繞開JVM的更底層功能,基於它的實現可以提高效率。但是,它是一把雙刃劍:正如它的名字所預示的那樣,它是Unsafe的,它所分配的記憶體需要手動free(不被GC回收)。Unsafe類,提供了JNI某些功能的簡單替代:確保高效性的同時,使事情變得更

Java Magic. Part 4: sun.misc.Unsafe

原文地址 譯文地址 譯者:許巧輝 校對:樑海艦 Java是一門安全的程式語言,防止程式設計師犯很多愚蠢的錯誤,它們大部分是基於記憶體管理的。但是,有一種方式可以有意的執行一些不安全、容易犯錯的操作,那就是使用Unsafe類。 本文是sun.misc.Unsafe公共API的簡要概述,及其一些有

java物件的記憶體佈局(二):利用sun.misc.Unsafe獲取欄位的偏移地址和讀取欄位的值

在上一篇文章中,我們列出了計算java物件大小的幾個結論以及jol工具的使用,jol工具的原始碼有興趣的可以去看下。現在我們利用JDK中的sun.misc.Unsafe來計算下欄位的偏移地址,一則驗證下之前文章中的結論,再則跟jol輸出結果對比下。如何獲取sun.misc.

JAVA筆記——道】JAVA記憶體操作 sun.misc.Unsafe

TIP:這是一個很危險的類,不熟悉情況下別用於生產環境 如果大家熟悉java concurrent,相信對Unsafe類不陌生。 我們知道JAVA作為高階語言的重要創新一點就是在於JVM的記憶體管理功能,這完全區別於C語言開發過程中需要對變數的記憶體分配小心

利用sun.misc.Unsafe獲取字段的偏移地址和讀取字段的值

com 如何 string ring rep 最好 lar 計算 .get 我們列出了計算java對象大小的幾個結論以及jol工具的使用,jol工具的源碼有興趣的可以去看下。現在我們利用JDK中的sun.misc.Unsafe來計算下字段的偏移地址,一則驗證下之前文章中的結

sun.misc.unsafe的使用

                    這個帖子是關於JAVA中鮮為人知的特性的後續更新,如果想得到下次線上討論的更新,請通過郵件訂閱,並且不要忘了在評論區留下你的意見和建議。    Java是一個安全的開發工具,它阻止開發人員犯很多低階的錯誤,而大部份的錯誤都是基於記憶體管理方面的。如果你想搞破壞,可以使用

Java 9中將移除 Sun.misc.Unsafe

原文連結    譯者:曲東方 災難將至,Java 9中將移除 Sun.misc.Unsafe Oracle 正在計劃在Java 9中去掉 sun.misc.Unsafe API。 這絕對將是一場災難,有可能會徹底破壞整個 java 生態圈。 幾乎每個使用 java開發的工具、軟體基礎設施、高效

使用sun.misc.Unsafe獲取java物件地址

在傳統的Java程式設計中,你將不再需要從記憶體中處理Java物件或位置。 當你在論壇上討論這一點,提出的第一個問題是為什麼你需要知道Java物件的地址? 它是一種有效的問題。 但以往,我們保留進行試驗的權利。探索未知領域的問題並沒有什麼錯。我想出了一個使用sun公司包

聊聊序列化(二)使用sun.misc.Unsafe繞過new機制來建立Java物件

在序列化的問題域裡面有一個常見的問題,就是反序列化時用何種方式來建立Java物件,因為反序列化的目的是把一段二進位制流轉化成一個物件。 在Java裡面建立物件有幾種方式: 1. 顯式地呼叫new語句, 比如 DemoClass demo = new DemoClass()

Java魔法——Unsafe應用解析

設定 編譯 之一 最新 str 操作數 校驗 init ostc 前言Unsafe是位於sun.misc包下的一個類,主要提供一些用於執行低級別、不安全操作的方法,如直接訪問系統內存資源、自主管理內存資源等,這些方法在提升Java運行效率、增強Java語言底層資源操作能力方

死磕 java魔法Unsafe解析

cat 序列化 簡介 分析 img 內部 基本功 圖片 resource 問題 (1)Unsafe是什麽? (2)Unsafe只有CAS的功能嗎? (3)Unsafe為什麽是不安全的? (4)怎麽使用Unsafe? 簡介 本章是java並發包專題的第一章,但是第一篇寫的卻不

一文了解sun.misc.Unsafe

類型轉換 鏈接 str DC arch lang 生態 rip export Java語言和JVM平臺已經度過了20歲的生日。它最初起源於機頂盒、移動設備和Java-Card,同時也應用在了各種服務器系統中,Java已成為物聯網(Internet of Things)的通用

JAVA總結(三)sun.jnu.encoding與file.encoding的區別

JAVA總結(三):sun.jnu.encoding與file.encoding的區別 2017年08月10日 19:39:24 蟻方陣 閱讀數:1704 標籤: java編碼位元組碼二進位制class檔案 更多 個人分類: jav

Java集合"隨機訪問" 的RandomAccess接口

ble java集合 .get 同時 ++ linked loop pri strac 引出RandomAccess接口 如果我們用Java做開發的話,最常用的容器之一就是List集合了,而List集合中用的較多的就是ArrayList 和 LinkedList 兩個類,這

sun.misc.Unsafe操作手冊

Java是一個安全的開發工具,它阻止開發人員犯很多低階的錯誤,而大部份的錯誤都是基於記憶體管理方面的。如果你想搞破壞,可以使用Unsafe這個類。這個類是屬於sun.* API中的類,並且它不是J2SE中真正的一部份,因此你可能找不到任何的官方文件,更可悲的是,它也沒有比較好的程式碼文件

Java集合LinkedHashMap

前言 今天繼續學習關於Map家族的另一個類 LinkedHashMap 。先說明一下,LinkedHashMap 是繼承於 HashMap 的,所以本文只針對 LinkedHashMap 的特性學習,跟HashMap 相關的一些特性就不做進一步的解析了,大家有疑

Java集合ArrayList

前言 今天學習一個Java集合類使用最多的類 ArrayList , ArrayList 繼承了 AbstractList,並實現了List 和 RandomAccess 等介面, public class ArrayList<E> extends

Java集合LinkedList

前言 上篇文裡講解了ArrayList ,它是基於List 介面來實現的,今天講解Java集合類中另一個跟List相關的集合類,叫做LinkedList 。 初識LinkedList LinkedList 是基於雙向連結串列實現的,也就是說,連結串列中任何一個

JAVA 常用正則表示式、Math、Random、System、Date和Calendar+程式碼

一、知識點體系圖 二、程式碼演示及注意事項 (1)正則表示式的程式碼例項 public class Test4 { public static void main(String[] args) { // 需求:獲取下面這個字串中由三個字元

import sun.misc.Unsafe

【注意】sun.misc.Unsafe這個類是jdk1.8才有的!所以在org.apache.hadoop.io.nativeio.NativeIO類中出現匯入Unsafe這個類的錯誤時,不要試圖去Maven下載jar包,更換jdk為1.8版本就可以了!親身經歷!!!!