1. 程式人生 > 其它 >淺析Java中的Unsafe類

淺析Java中的Unsafe類

淺析Java中的Unsafe類

Unsafe類

Unsafe類是 Java 中一些進行底層操作和不安全操作的方法集合,提供了一些可以直接操控記憶體和執行緒的低層次操作。這個不安全的類提供了一個觀察 HotSpot JVM 內部結構並且可以對其進行修改。有時它可以被用來在不適用 C++ 除錯的情況下學習虛擬機器內部結構,有時也可以被拿來做效能監控和開發工具。

本篇僅簡單介紹,不涉及複雜使用和底層原理。

1. 獲取Unsafe例項

private static final Unsafe theUnsafe = new Unsafe();

public static Unsafe getUnsafe() {
    return theUnsafe;
}

Unsafe類提供了一個十分樸素的get方法,用於獲取一個static finalUnsafe例項。

2. 獲取當前系統資訊

2.1 獲取本機地址大小和頁面大小

public int addressSize() {
    return ADDRESS_SIZE;
}

public int pageSize() { 
	return PAGE_SIZE; 
}

addressSize()方法和pageSize()方法分別可以獲取本機的地址大小(實際即為字長)和頁面大小。

2.2 獲取本機是大端編址/小端編址

public final boolean isBigEndian() { return BIG_ENDIAN; }

2.3 檢查是否可以在不對齊地址上訪問

/**
 * @return Returns true if this platform is capable of performing
 * accesses at addresses which are not aligned for the type of the
 * primitive type being accessed, false otherwise.
 */
public final boolean unalignedAccess() { return UNALIGNED_ACCESS; }

3. allocate分配方法

3.1 allocateInstance方法

@HotSpotIntrinsicCandidate
public native Object allocateInstance(Class<?> cls)
    throws InstantiationException;

allocateInstance方法可以建立一個cls引數對應型別的物件,同時不呼叫任何建構函式。

3.2 allocateUninitializedArray方法

public Object allocateUninitializedArray(Class<?> componentType, int length) {
   if (componentType == null) {
       throw new IllegalArgumentException("Component type is null");
   }
   if (!componentType.isPrimitive()) {
       throw new IllegalArgumentException("Component type is not primitive");
   }
   if (length < 0) {
       throw new IllegalArgumentException("Negative length");
   }
   return allocateUninitializedArray0(componentType, length);
}

@HotSpotIntrinsicCandidate
private Object allocateUninitializedArray0(Class<?> componentType, int length) {
    // These fallbacks provide zeroed arrays, but intrinsic is not required to
    // return the zeroed arrays.
    if (componentType == byte.class)    return new byte[length];
    if (componentType == boolean.class) return new boolean[length];
    if (componentType == short.class)   return new short[length];
    if (componentType == char.class)    return new char[length];
    if (componentType == int.class)     return new int[length];
    if (componentType == float.class)   return new float[length];
    if (componentType == long.class)    return new long[length];
    if (componentType == double.class)  return new double[length];
    return null;
}

allocateUninitializedArray()方法可以分配一個指定大小,但未進行初始化的陣列。未進行歸零的結果是這個未初始化陣列中都是各種無意義的垃圾資料。

3.3 allocateMemory方法

public long allocateMemory(long bytes) {
    allocateMemoryChecks(bytes);

    if (bytes == 0) {
        return 0;
    }

    long p = allocateMemory0(bytes);
    if (p == 0) {
        throw new OutOfMemoryError();
    }

    return p;
}

private native long allocateMemory0(long bytes);

allocateMemory()方法分配一個新的本機記憶體塊,記憶體內容未初始化,一般是垃圾資料。

3.4 freeMemory方法

public void freeMemory(long address) {
    freeMemoryChecks(address);

    if (address == 0) {
        return;
    }

    freeMemory0(address);
}

private native void freeMemory0(long address);

freeMeomory()方法釋放由allocateMemory()申請的一個已分配的記憶體塊。

3.5 reallocateMememory方法

public long reallocateMemory(long address, long bytes) {
    reallocateMemoryChecks(address, bytes);

    if (bytes == 0) {
        freeMemory(address);
        return 0;
    }

    long p = (address == 0) ? allocateMemory0(bytes) : reallocateMemory0(address, bytes);
    if (p == 0) {
        throw new OutOfMemoryError();
    }

    return p;
}

reallocateMemory()方法可以修改一個由allocate()方法申請的記憶體塊的大小。如果修改後的大小比原塊小,那麼截斷。如果修改後的大小比原塊大,那麼超出的空間將是一片垃圾資料。

4. 陣列元素定位方法

public int arrayIndexScale(Class<?> arrayClass) {
    if (arrayClass == null) {
        throw new NullPointerException();
    }

    return arrayIndexScale0(arrayClass);
}

private native int arrayIndexScale0(Class<?> arrayClass);

arrayIndexScale()方法可以獲取引數arrayClass的對應比例因子,可以和偏移量結合定位到陣列中某個元素。

public int arrayBaseOffset(Class<?> arrayClass) {
    if (arrayClass == null) {
        throw new NullPointerException();
    }

    return arrayBaseOffset0(arrayClass);
}

private native int arrayBaseOffset0(Class<?> arrayClass);

arrayBaseOffset()方法可以獲取引數arrayClass的對應陣列中,第一個元素的偏移量。可以同上面的arrayIndexScale()方法解和定位到陣列中某個元素的地址。

5. byte與bool相互轉換方法

@ForceInline
private boolean byte2bool(byte b) {
    return b != 0;
}

@ForceInline
private byte bool2byte(boolean b) {
return b ? (byte)1 : (byte)0;
}

Unsafe類提供了在bool和byte之間轉換的方法。

注:@ForceInline註解強制要求不對這個方法進行方法內聯。

6. CAS方法

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-DIqq4k5x-1635390819612)(G:\markdown\Java\知識點\image-20211027145455143.png)]Unsafe類中給出了大量比較並設定和比較並交換方法。這些方法都指向某個native的方法作為底層實現。

且這些方法都由@HotSpotIntrinsicCandidate所標記。

7. 記憶體塊拷貝方法

7.1 copyMeomry方法

public void copyMemory(long srcAddress, long destAddress, long bytes) {
    copyMemory(null, srcAddress, null, destAddress, bytes);
}

public void copyMemory(Object srcBase, long srcOffset,
                       Object destBase, long destOffset,
                       long bytes) {
    copyMemoryChecks(srcBase, srcOffset, destBase, destOffset, bytes);

    if (bytes == 0) {
        return;
    }

    copyMemory0(srcBase, srcOffset, destBase, destOffset, bytes);
}

@HotSpotIntrinsicCandidate
private native void copyMemory0(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes);

copyMemory()方法將指定記憶體塊中的內容在記憶體的另一個位置設定一個拷貝。

7.2 copySwapMemory方法

public void copySwapMemory(long srcAddress, long destAddress, long bytes, long elemSize) {
    copySwapMemory(null, srcAddress, null, destAddress, bytes, elemSize);
}

public void copySwapMemory(Object srcBase, long srcOffset,
                           Object destBase, long destOffset,
                           long bytes, long elemSize) {
    copySwapMemoryChecks(srcBase, srcOffset, destBase, destOffset, bytes, elemSize);

    if (bytes == 0) {
        return;
    }

    copySwapMemory0(srcBase, srcOffset, destBase, destOffset, bytes, elemSize);
}

private native void copySwapMemory0(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes, long elemSize);

copySwapMemory()方法將指定記憶體塊中的內容與記憶體中另一個位置中的內容進行交換。

8. 定義類方法

8.1 defineAnonymousClass方法

public Class<?> defineAnonymousClass(Class<?> hostClass, byte[] data, Object[] cpPatches) {
    if (hostClass == null || data == null) {
        throw new NullPointerException();
    }
    if (hostClass.isArray() || hostClass.isPrimitive()) {
        throw new IllegalArgumentException();
    }

    return defineAnonymousClass0(hostClass, data, cpPatches);
}

defineAnonymousClass方法可以建立一個不被虛擬機器及系統字典所知的類。

8.2 defineClass方法

public Class<?> defineClass(String name, byte[] b, int off, int len,
                            ClassLoader loader,
                            ProtectionDomain protectionDomain) {
    if (b == null) {
        throw new NullPointerException();
    }
    if (len < 0) {
        throw new ArrayIndexOutOfBoundsException();
    }

    return defineClass0(name, b, off, len, loader, protectionDomain);
}

public native Class<?> defineClass0(String name, byte[] b, int off, int len,
                                    ClassLoader loader,
                                    ProtectionDomain protectionDomain);

defineClass方法告訴 VM 定義一個類,而不進行安全檢查。 預設情況下,這個類的類載入器和保護域來自呼叫者的類。

9. 清理IO緩衝區

public void invokeCleaner(java.nio.ByteBuffer directBuffer) {
    if (!directBuffer.isDirect())
        throw new IllegalArgumentException("buffer is non-direct");

    DirectBuffer db = (DirectBuffer) directBuffer;
    if (db.attachment() != null)
        throw new IllegalArgumentException("duplicate or slice");

    Cleaner cleaner = db.cleaner();
    if (cleaner != null) {
        cleaner.clean();
    }
}

10. 新增記憶體屏障

@HotSpotIntrinsicCandidate
public native void loadFence();

@HotSpotIntrinsicCandidate
public native void storeFence();

loadFence()方法可以新增一個 Load - Load 型的記憶體屏障。

storeFence()方法可以新增一個 Store - Store 型的記憶體屏障。

參考《Java併發程式設計的藝術》p26

11. 通過物件和偏移量直接獲取對應資料的get方法

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-MwgDgmb5-1635390819616)(G:\markdown\Java\知識點\image-20211027154806221.png)]

Unsafe類中提供了大量的get方法,可以通過物件和偏移量,定位獲取到對應的值。並同時做addswap等操作。下面是簡單的示例。

// getAddress方法可以獲取對應位置的本機指標
@ForceInline
public long getAddress(Object o, long offset) {
    if (ADDRESS_SIZE == 4) {
        return Integer.toUnsignedLong(getInt(o, offset));
    } else {
        return getLong(o, offset);
    }
}

@HotSpotIntrinsicCandidate
public native int getInt(Object o, long offset);

@HotSpotIntrinsicCandidate
public final int getAndSetInt(Object o, long offset, int newValue) {
    int v;
    do {
        v = getIntVolatile(o, offset);
    } while (!weakCompareAndSetInt(o, offset, v, newValue));
    return v;
}

@ForceInline
public final int getAndBitwiseAndInt(Object o, long offset, int mask) {
    int current;
    do {
        current = getIntVolatile(o, offset);
    } while (!weakCompareAndSetInt(o, offset,
                                   current, current & mask));
    return current;
}

12. 通過物件和偏移量直接向指定記憶體位置賦值的put方法

Unsafe類提供的put方法可以通過物件和偏移量,向指定的位置賦值。下面是簡單的示例

@ForceInline
public void putAddress(Object o, long offset, long x) {
    if (ADDRESS_SIZE == 4) {
        putInt(o, offset, (int)x);
    } else {
        putLong(o, offset, x);
    }
}

@HotSpotIntrinsicCandidate
public native void putInt(Object o, long offset, int x);

@HotSpotIntrinsicCandidate
public native void putReference(Object o, long offset, Object x);

13. 用指定字元填充記憶體

public void setMemory(Object o, long offset, long bytes, byte value) {
    setMemoryChecks(o, offset, bytes, value);

    if (bytes == 0) {
        return;
    }

    setMemory0(o, offset, bytes, value);
}

private native void setMemory0(Object o, long offset, long bytes, byte value);

setMemory()方法可以用指定的字元填充一塊記憶體。