Java-Unsafe


Unsafe 是 sun.misc 包下的一個類,可以直接操作堆外內存,可以隨意查看及修改 JVM 中運行時的數據,使 Java 語言擁有了類似 C 語言指針一樣操作內存空間的能力。

Unsafe 的操作粒度不是類,而是內存地址和所對應的數據,增強了 Java 語言操作底層資源的能力。

 

一、獲得 Unsafe 實例

查看 Unsafe.java 源碼:https://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/3ef3348195ff/src/share/classes/sun/misc/Unsafe.java

public final class Unsafe {
    private Unsafe() {}

    // 單例對象
    private static final Unsafe theUnsafe = new Unsafe();

    @CallerSensitive
    public static Unsafe getUnsafe() {
        Class<?> caller = Reflection.getCallerClass();
        // 僅在引導類加載器 BootstrapClassLoader 加載時才合法
        if (!VM.isSystemDomainLoader(caller.getClassLoader()))
            throw new SecurityException("Unsafe");
        return theUnsafe;
    }
}

由此可以看出自己寫的類即不能 new Unsafe() 對象也不能調用其 getUnsafe() 方法。

若想使用這個類只能通過其它方法,有如下兩個可行方案。

1.讓自己寫的類使用 BootstrapClassLoader 加載

# unix 使用:號,windows 使用;號,這里以 windows 為例,使用了 Unsafe 類的 jar 包路徑為 /hone/myUnsafe.jar
java -Xbootclasspath/a;/home/myUnsafe.jar com.unsafeTest

2.通過反射獲取單例對象 theUnsafe

private static Unsafe reflectGetUnsafe() {
    try {
        // 獲得 theUnsafe 屬性對象
        Field field = Unsafe.class.getDeclaredField("theUnsafe");
        // 取消權限控制檢查,讓其可獲得 private 修飾屬性
        field.setAccessible(true);
        // 獲得 Unsafe.class 的 theUnsafe 屬性(如果底層字段是一個靜態字段,則忽略 obj 參數;它可能為 null)
        return (Unsafe) field.get(null);
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
}

 

二、Unsafe 常用 API 介紹

Unsafe 類大部分都是 native 方法,具體實現由 JVM 完成:https://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/7576bbd5a03c/src/share/vm/prims/unsafe.cpp

1.內存操作(堆外內存)

// 分配內存, 相當於 C++ 的 malloc 函數
public native long allocateMemory(long bytes);

// 擴充內存
public native long reallocateMemory(long address, long bytes);

// 釋放內存
public native void freeMemory(long address);

// 在給定的內存塊中設置值
public native void setMemory(Object o, long offset, long bytes, byte value);

// 內存拷貝
public native void copyMemory(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes);

// 獲取給定地址值,忽略修飾限定符的訪問限制。與此類似操作還有: getInt,getDouble,getLong,getChar 等
public native Object getObject(Object o, long offset);

// 為給定地址設置值,忽略修飾限定符的訪問限制,與此類似操作還有: putInt,putDouble,putLong,putChar 等
public native void putObject(Object o, long offset, Object x);

// 獲取給定地址的 byte 類型的值(當且僅當該內存地址為 allocateMemory 分配時,此方法結果為確定的)
public native byte getByte(long address);

// 為給定地址設置 byte 類型的值(當且僅當該內存地址為 allocateMemory 分配時,此方法結果才是確定的)
public native void putByte(long address, byte x);

2.CAS

/**
 * CAS
 *
 * @param o        包含要修改field的對象
 * @param offset   對象中某field的偏移量
 * @param expected 期望值
 * @param update   更新值
 * @return         true | false
 */
public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object update);

public final native boolean compareAndSwapInt(Object o, long offset, int expected, int update);

public final native boolean compareAndSwapLong(Object o, long offset, long expected, long update);

3.線程調度

// 阻塞線程
public native void park(boolean isAbsolute, long time);

// 取消阻塞線程
public native void unpark(Object thread);

// 獲得對象鎖(可重入鎖)
@Deprecated
public native void monitorEnter(Object o);

// 釋放對象鎖
@Deprecated
public native void monitorExit(Object o);

// 嘗試獲取對象鎖
@Deprecated
public native boolean tryMonitorEnter(Object o);

4.Class 相關

// 獲取給定靜態字段的內存地址偏移量,這個值對於給定的字段是唯一且固定不變的
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);

5.對象操作

// 返回對象成員屬性在內存地址相對於此對象的內存地址的偏移量
public native long objectFieldOffset(Field f);

// 獲得給定對象的指定地址偏移量的值,與此類似操作還有:getInt,getDouble,getLong,getChar等
public native Object getObject(Object o, long offset);

// 給定對象的指定地址偏移量設值,與此類似操作還有:putInt,putDouble,putLong,putChar等
public native void putObject(Object o, long offset, Object x);

// 從對象的指定偏移量處獲取變量的引用,使用volatile的加載語義
public native Object getObjectVolatile(Object o, long offset);

// 存儲變量的引用到對象的指定的偏移量處,使用 volatile 的存儲語義
public native void putObjectVolatile(Object o, long offset, Object x);

// 有序、延遲版本的 putObjectVolatile 方法,不保證值的改變被其他線程立即看到。只有在 field 被 volatile 修飾符修飾時有效
public native void putOrderedObject(Object o, long offset, Object x);

// 繞過構造方法、初始化代碼來創建對象
public native Object allocateInstance(Class<?> cls) throws InstantiationException;

6.數組相關

// 返回數組中第一個元素的偏移地址
public native int arrayBaseOffset(Class<?> arrayClass);

// 返回數組中一個元素占用的大小
public native int arrayIndexScale(Class<?> arrayClass);

7.內存屏障

// 內存屏障,禁止 load 操作重排序。屏障前的 load 操作不能被重排序到屏障后,屏障后的 load 操作不能被重排序到屏障前
public native void loadFence();

// 內存屏障,禁止 store 操作重排序。屏障前的 store 操作不能被重排序到屏障后,屏障后的 store 操作不能被重排序到屏障前
public native void storeFence();

// 內存屏障,禁止 load、store 操作重排序
public native void fullFence();

8.系統相關

// 返回系統指針的大小。返回值為 4(32位系統)或 8(64位系統)。
public native int addressSize();

// 內存頁的大小,此值為 2 的冪次方。
public native int pageSize();

 

三、簡單使用

創建對象並修改其屬性

跳過對象初始化階段,或繞過構造器的安全檢查,或實例化一個沒有任何公共構造器的類。

反射可以實現相同的功能。但值得關注的是,我們可以修改任何對象,甚至沒有這些對象的引用。

class User {
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

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

    @Override
    public String toString() {
        return "User{" + "name='" + name + '\'' + ", age=" + age + '}';
    }
}


public static void main(String[] args) throws Exception {
    Class userClass = User.class;
    // 避開構造方法初始化對象
    User user = (User) unsafe.allocateInstance(userClass);

    user.setAge(20);
    System.out.println(user);

    // 修改對象成員值
    Field field = User.class.getDeclaredField("age");
    unsafe.putInt(user, unsafe.objectFieldOffset(field), 8);
    System.out.println(user);
}

private static Unsafe unsafe;

static {
    unsafe = reflectGetUnsafe();
}

private static Unsafe reflectGetUnsafe() {
    try {
        Field field = Unsafe.class.getDeclaredField("theUnsafe");
        field.setAccessible(true);
        return (Unsafe) field.get(null);
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
}

大數組

Java 數組大小的最大值為 Integer.MAX_VALUE。使用直接內存分配,我們創建的數組大小受限於堆大小。

使用堆外內存(off-heap memory)技術,這種方式的內存分配不在堆上,且不受 GC 管理,所以必須小心 Unsafe.freeMemory() 的使用。它也不執行任何邊界檢查,所以任何非法訪問可能會導致 JVM 崩潰。

class SuperArray {
    private final static int BYTE = 1;
    private long size;
    private long address;
    private static Unsafe unsafe;

    static {
        unsafe = reflectGetUnsafe();
    }

    public static Unsafe getUnsafe() {
        return unsafe;
    }

    public long getAddress() {
        return address;
    }

    private static Unsafe reflectGetUnsafe() {
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            return (Unsafe) field.get(null);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public SuperArray(long size) {
        this.size = size;
        address = unsafe.allocateMemory(size * BYTE);
    }

    public void set(long i, byte value) {
        unsafe.putByte(address + i * BYTE, value);
    }

    public int get(long idx) {
        return unsafe.getByte(address + idx * BYTE);
    }

    public long size() {
        return size;
    }
}

public static void main(String[] args) {
    int sum = 0;
    long SUPER_SIZE = (long) Integer.MAX_VALUE * 2;
    SuperArray array = new SuperArray(SUPER_SIZE);
    System.out.println("Array size:" + array.size()); // 4294967294
    for (int i = 0; i < 100; i++) {
        array.set((long) Integer.MAX_VALUE + i, (byte) 3);
        sum += array.get((long) Integer.MAX_VALUE + i);
    }
    System.out.println("Sum of 100 elements:" + sum);  // 300
    SuperArray.getUnsafe().freeMemory(array.getAddress());
}

 


https://tech.meituan.com/2019/02/14/talk-about-java-magic-class-unsafe.html

https://ifeve.com/sun-misc-unsafe/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM