Java基礎之Unsafe內存操作不安全類詳解
簡介
Unsafe類使Java擁有瞭像C語言的指針一樣操作內存空間的能力,直接操作內存就意味著
1、不受jvm管理,也就意味著無法被GC,需要我們手動GC,稍有不慎就會出現內存泄漏。
2、Unsafe的不少方法中必須提供原始地址(內存地址)和被替換對象的地址,偏移量要自己計算,一旦出現問題就是JVM崩潰級別的異常,會導致整個JVM實例崩潰,表現為應用程序直接crash掉。
3、直接操作內存,也意味著其速度更快,在高並發的條件之下能夠很好地提高效率。
Unsafe 類
public final class Unsafe
Unsafe類是”final”的,不允許繼承。
Unsafe 屬性
private static final Unsafe theUnsafe; public static final int INVALID_FIELD_OFFSET = -1; public static final int ARRAY_BOOLEAN_BASE_OFFSET; public static final int ARRAY_BYTE_BASE_OFFSET; public static final int ARRAY_SHORT_BASE_OFFSET; public static final int ARRAY_CHAR_BASE_OFFSET; public static final int ARRAY_INT_BASE_OFFSET; public static final int ARRAY_LONG_BASE_OFFSET; public static final int ARRAY_FLOAT_BASE_OFFSET; public static final int ARRAY_DOUBLE_BASE_OFFSET; public static final int ARRAY_OBJECT_BASE_OFFSET; public static final int ARRAY_BOOLEAN_INDEX_SCALE; public static final int ARRAY_BYTE_INDEX_SCALE; public static final int ARRAY_SHORT_INDEX_SCALE; public static final int ARRAY_CHAR_INDEX_SCALE; public static final int ARRAY_INT_INDEX_SCALE; public static final int ARRAY_LONG_INDEX_SCALE; public static final int ARRAY_FLOAT_INDEX_SCALE; public static final int ARRAY_DOUBLE_INDEX_SCALE; public static final int ARRAY_OBJECT_INDEX_SCALE; public static final int ADDRESS_SIZE;
這些屬性都是在類加載時初始化,它們都是一些類型數組指針。
Unsafe 靜態加載
static { registerNatives(); Reflection.registerMethodsToFilter(Unsafe.class, new String[]{"getUnsafe"}); theUnsafe = new Unsafe(); ARRAY_BOOLEAN_BASE_OFFSET = theUnsafe.arrayBaseOffset(boolean[].class); ARRAY_BYTE_BASE_OFFSET = theUnsafe.arrayBaseOffset(byte[].class); ARRAY_SHORT_BASE_OFFSET = theUnsafe.arrayBaseOffset(short[].class); ARRAY_CHAR_BASE_OFFSET = theUnsafe.arrayBaseOffset(char[].class); ARRAY_INT_BASE_OFFSET = theUnsafe.arrayBaseOffset(int[].class); ARRAY_LONG_BASE_OFFSET = theUnsafe.arrayBaseOffset(long[].class); ARRAY_FLOAT_BASE_OFFSET = theUnsafe.arrayBaseOffset(float[].class); ARRAY_DOUBLE_BASE_OFFSET = theUnsafe.arrayBaseOffset(double[].class); ARRAY_OBJECT_BASE_OFFSET = theUnsafe.arrayBaseOffset(Object[].class); ARRAY_BOOLEAN_INDEX_SCALE = theUnsafe.arrayIndexScale(boolean[].class); ARRAY_BYTE_INDEX_SCALE = theUnsafe.arrayIndexScale(byte[].class); ARRAY_SHORT_INDEX_SCALE = theUnsafe.arrayIndexScale(short[].class); ARRAY_CHAR_INDEX_SCALE = theUnsafe.arrayIndexScale(char[].class); ARRAY_INT_INDEX_SCALE = theUnsafe.arrayIndexScale(int[].class); ARRAY_LONG_INDEX_SCALE = theUnsafe.arrayIndexScale(long[].class); ARRAY_FLOAT_INDEX_SCALE = theUnsafe.arrayIndexScale(float[].class); ARRAY_DOUBLE_INDEX_SCALE = theUnsafe.arrayIndexScale(double[].class); ARRAY_OBJECT_INDEX_SCALE = theUnsafe.arrayIndexScale(Object[].class); ADDRESS_SIZE = theUnsafe.addressSize(); } private static native void registerNatives();
Unsafe 構造函數
private Unsafe() { }
Unsafe 對象不能直接通過 new Unsafe(),它的構造函數是私有的。
Unsafe 實例化方法
public static Unsafe getUnsafe() { Class var0 = Reflection.getCallerClass(); if (!VM.isSystemDomainLoader(var0.getClassLoader())) { throw new SecurityException("Unsafe"); } else { return theUnsafe; } }
getUnsafe 隻能從引導類加載器(bootstrap class loader)加載,非啟動類加載器直接調用 Unsafe.getUnsafe() 方法會拋出 SecurityException 異常。解決辦法:
1、可以令代碼 ” 受信任 “。運行程序時,通過 JVM 參數設置 bootclasspath 選項,指定系統類路徑加上使用的一個 Unsafe 路徑。
java -Xbootclasspath:/usr/jdk1.7.0/jre/lib/rt.jar:. com.Test
2、通過 Java 反射機制,暴力獲取。
Field field = Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); Unsafe unsafe = (Unsafe) field.get(null);
Unsafe 內存管理
// 獲取本地指針的大小(單位是byte),通常值為4或者8。常量ADDRESS_SIZE就是調用此方法。 public native int addressSize(); // 獲取本地內存的頁數,此值為2的冪次方。 public native int pageSize(); // 分配一塊新的本地內存,通過bytes指定內存塊的大小(單位是byte),返回新開辟的內存的地址。 public native long allocateMemory(long var1); // 通過指定的內存地址address重新調整本地內存塊的大小,調整後的內存塊大小通過bytes指定(單位為byte)。 public native long reallocateMemory(long var1, long var3); // 將給定內存塊中的所有字節設置為固定值(通常是0) 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);
註意:allocateMemory方法申請的內存,將直接脫離jvm,gc將無法管理該方式申請的內存,用完一定要手動釋放內存,防止內存溢出;
JDK中示例:ByteBuffer.allocateDirect(int capacity)使用DirectByteBuffer,DirectByteBuffer中就是用allocateMemory申請堆外內存。
Unsafe 獲取偏移量
// 返回指定變量所屬類中的內存偏移量 public native long objectFieldOffset(Field var1); // 獲取數組中第一個元素的地址 public native int arrayBaseOffset(Class<?> var1); // 獲取靜態變量地址偏移值 public native long staticFieldOffset(Field var1); // 其實就是數據中元素偏移地址的增量,數組中的元素的地址是連續的 public native int arrayIndexScale(Class<?> var1);
Unsafe 檢查類初始化
// 檢測給定的類是否需要初始化。 // 當ensureClassInitialized方法不生效的時候才返回false public native boolean shouldBeInitialized(Class<?> c); // 檢測給定的類是否已經初始化。 public native void ensureClassInitialized(Class<?> c);
Unsafe 從指定位置讀取
// 從指定內存地址處開始讀取一個byte public native byte getByte(long var1); // 從指定內存地址處開始讀取一個short public native short getShort(long var1); // 從指定內存地址處開始讀取一個char public native char getChar(long var1); // 從指定內存地址處開始讀取一個int public native int getInt(long var1); // 從指定內存地址處開始讀取一個long public native long getLong(long var1); // 從指定內存地址處開始讀取一個float public native float getFloat(long var1); // 從指定內存地址處開始讀取一個double public native double getDouble(long var1);
Unsafe 向指定位置寫值
// 向指定位置寫入一個int public native void putInt(long var1, int var3); // 向指定位置寫入一個char public native void putChar(long var1, char var3); // 向指定位置寫入一個byte public native void putByte(long var1, byte var3); // 向指定位置寫入一個short public native void putShort(long var1, short var3); // 向指定位置寫入一個long public native void putLong(long var1, long var3); // 向指定位置寫入一個float public native void putFloat(long var1, float var3); // 向指定位置寫入一個double public native void putDouble(long var1, double var3);
Unsafe 對象操作
從指定偏移量處讀取對象屬性(非主存)
public native int getInt(Object var1, long var2); public native Object getObject(Object var1, long var2); public native boolean getBoolean(Object var1, long var2); public native byte getByte(Object var1, long var2); public native short getShort(Object var1, long var2); public native char getChar(Object var1, long var2); public native long getLong(Object var1, long var2); public native float getFloat(Object var1, long var2); public native double getDouble(Object var1, long var2);
向指定偏移量處修改對象屬性(非主存)
public native void putInt(Object var1, long var2, int var4); public native void putObject(Object var1, long var2, Object var4); public native void putBoolean(Object var1, long var2, boolean var4); public native void putByte(Object var1, long var2, byte var4); public native void putShort(Object var1, long var2, short var4); public native void putChar(Object var1, long var2, char var4); public native void putLong(Object var1, long var2, long var4); public native void putFloat(Object var1, long var2, float var4); public native void putDouble(Object var1, long var2, double var4);
向指定偏移量處修改對象屬性(主存)
public native Object getObjectVolatile(Object var1, long var2); public native int getIntVolatile(Object var1, long var2); public native boolean getBooleanVolatile(Object var1, long var2); public native byte getByteVolatile(Object var1, long var2); public native short getShortVolatile(Object var1, long var2); public native char getCharVolatile(Object var1, long var2); public native long getLongVolatile(Object var1, long var2); public native float getFloatVolatile(Object var1, long var2); public native double getDoubleVolatile(Object var1, long var2);
向指定偏移量處修改對象屬性(主存)
public native void putObjectVolatile(Object var1, long var2, Object var4); public native void putIntVolatile(Object var1, long var2, int var4); public native void putBooleanVolatile(Object var1, long var2, boolean var4); public native void putByteVolatile(Object var1, long var2, byte var4); public native void putShortVolatile(Object var1, long var2, short var4); public native void putCharVolatile(Object var1, long var2, char var4); public native void putLongVolatile(Object var1, long var2, long var4); public native void putFloatVolatile(Object var1, long var2, float var4); public native void putDoubleVolatile(Object var1, long var2, double var4); public native void putOrderedObject(Object var1, long var2, Object var4); 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);
Unsafe 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操作,本質更新對象中指定偏移量的屬性,當原值為var4時才會更新成var5並返回true,否則返回false。
舉例:volatile i=0;有多個線程修改i的值,A線程隻有在i=1時修改為2,如果代碼如下
if (i == 1) {i = 2;}
這樣時有問題的,if比較完i可能已經被別人修改瞭,這種場景特別適合CAS,使用CAS代碼如下
boolean isUpdate = compareAndSwapInt(object, offset, 1, 2)
相當於讀->判斷->寫一次搞定(實在不能理解CAS,可以這麼理解)
Unsafe 線程的掛起和恢復
public native void park(boolean var1, long var2);
阻塞當前線程直到一個unpark方法出現(被調用)、一個用於unpark方法已經出現過(在此park方法調用之前已經調用過)、線程被中斷或者time時間到期(也就是阻塞超時)。在time非零的情況下,如果isAbsolute為true,time是相對於新紀元之後的毫秒,否則time表示納秒。這個方法執行時也可能不合理地返回(沒有具體原因)。並發包java.util.concurrent中的框架對線程的掛起操作被封裝在LockSupport類中,LockSupport類中有各種版本pack方法,但最終都調用瞭Unsafe#park()方法。
public native void unpark(Object var1);
釋放被park創建的在一個線程上的阻塞。這個方法也可以被使用來終止一個先前調用park導致的阻塞。這個操作是不安全的,因此必須保證線程是存活的(thread has not been destroyed)。從Java代碼中判斷一個線程是否存活的是顯而易見的,但是從native代碼中這機會是不可能自動完成的。
Unsafe 內存屏障
public native void loadFence();
在該方法之前的所有讀操作,一定在load屏障之前執行完成。
public native void storeFence();
在該方法之前的所有寫操作,一定在store屏障之前執行完成
public native void fullFence();
在該方法之前的所有讀寫操作,一定在full屏障之前執行完成,這個內存屏障相當於上面兩個(load屏障和store屏障)的合體功能。
Unsafe 其他
public native int getLoadAverage(double[] loadavg, int nelems);
獲取系統的平均負載值,loadavg這個double數組將會存放負載值的結果,nelems決定樣本數量,nelems隻能取值為1到3,分別代表最近1、5、15分鐘內系統的平均負載。如果無法獲取系統的負載,此方法返回-1,否則返回獲取到的樣本數量(loadavg中有效的元素個數)。實驗中這個方法一直返回-1,其實完全可以使用JMX中的相關方法替代此方法。
public native void throwException(Throwable ee);
繞過檢測機制直接拋出異常。
到此這篇關於Java基礎之Unsafe內存操作不安全類詳解的文章就介紹到這瞭,更多相關Unsafe內存操作不安全類內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- 你一定不知道的Java Unsafe用法詳解
- Java Unsafe 類的講解
- Java 中的 Unsafe 魔法類的作用大全
- Java基本數據類型族譜與易錯點梳理解析
- Java字符串編碼解碼性能提升的技巧分享