簡介
-
Unsafe類具有私有構造函數,並且是單例的。雖然Unsafe具有靜態的getUnsafe()方法,但是直接調用Unsafe.getUnsafe(),則可能會收到SecurityException,此方法僅可被用於用於受信任的代碼。如果代碼是受信任的,會判斷是不是系統類加載器
@CallerSensitive public static Unsafe getUnsafe() { Class<?> caller = Reflection.getCallerClass(); if (!VM.isSystemDomainLoader(caller.getClassLoader())) throw new SecurityException("Unsafe"); return theUnsafe; }
-
代碼變爲受信任的的方式
1. 增加JVM虛擬機選項 java -Xbootclasspath:/usr/jdk1.7.0/jre/lib/rt.jar: ${path} // 其中path爲調用Unsafe相關方法的類所在jar包路徑
-
通過反射獲取Unsafe
Field f = Unsafe.class.getDeclaredField("theUnsafe"); f.setAccessible(true); Unsafe unsafe = (Unsafe) f.get(null);
方法
-
Unsafe大部分方法都是native方法,分爲幾類
-
Info:主要返回系統相關信息的方法
//返回系統指針的大小。返回值爲4(32位系統)或 8(64位系統)。 public native int addressSize(); //內存頁的大小,此值爲2的冪次方。 public native int pageSize(); //8 System.out.println(unsafe.addressSize()); //4096 System.out.println(unsafe.pageSize());
-
Objects:提供對象及其字段操作的方法
//返回對象成員屬性在內存地址相對於此對象的內存地址的偏移量 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; 1. 創建User類 class User { private int age; private String name; ...省略... } 2. 實例化User User user = (User) unsafe.allocateInstance(User.class); user.setAge(18); user.setName("jannal"); /** * age:對應的內存偏移地址12 * name:對應的內存偏移地址16 */ for (Field field : User.class.getDeclaredFields()) { System.out.println(field.getName() + ":對應的內存偏移地址" + unsafe.objectFieldOffset(field)); }
-
Classes:提供用於類和靜態字段操作的方法
//獲取給定靜態字段的內存地址偏移量,這個值對於給定的字段是唯一且固定不變的 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);
-
Array:數組操作方法
//返回數組中第一個元素的偏移地址 public native int arrayBaseOffset(Class<?> arrayClass); //返回數組中一個元素佔用的大小 public native int arrayIndexScale(Class<?> arrayClass);
-
Synchronization:主要提供低級別同步原語
//取消阻塞線程 public native void unpark(Object thread); //阻塞線程 public native void park(boolean isAbsolute, long time); //獲得對象鎖(可重入鎖) @Deprecated public native void monitorEnter(Object o); //釋放對象鎖 @Deprecated public native void monitorExit(Object o); //嘗試獲取對象鎖 @Deprecated public native boolean tryMonitorEnter(Object o); /*** * @param obj 包含要修改field的對象 * @param offset obj中整型field的偏移量 * @param expect 期望field中存在的值 * @param update 如果期望值expect與field的當前值相同,設置filed的值爲這個新值 * @return true 如果field的值被更改返回true */ public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x); public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object update); public final native boolean compareAndSwapLong(Object o, long offset, long expected, long update);
-
Memory:直接內存訪問方法,繞過JVM堆直接操縱本地內存
//分配內存, 相當於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);
方法詳解
allocateInstance
-
如果要跳過對象初始化或者繞過構造函數的安全檢查或者類沒有public的構造函數時,可以使用
allocateInstance
public class Person { private long age; public Person() { this.age = 10; } public long age() { return age; } } @Test public void testAllocateInstance() throws Exception { Unsafe unsafe = unsafe(); Person person = new Person(); //輸出10 System.out.println(person.age()); person = Person.class.newInstance(); //輸出10 System.out.println(person.age()); person = (Person) unsafe.allocateInstance(Person.class); //輸出0 System.out.println(person.age()); }
拋出異常
-
如果不喜歡Checked Exception,可以使用如下方式,此方法拋出檢查異常,但是代碼沒有被強制捕獲或重新拋出它,就像運行時異常一樣
getUnsafe().throwException(new IOException());
SizeOf
-
使用
objectFieldOffset()
方法,我們可以實現C樣式的sizeof
函數。此實現返回對象的淺層大小( shallow size of object)/** * 實際上,爲了獲得良好,安全和準確的sizeof函數,最好使用java.lang.instrument包, * 但是它需要在JVM中指定agent選項。 */ public static long sizeOf(Object o) { Unsafe unsafe = null; try { unsafe = unsafe(); } catch (Exception e) { throw new RuntimeException(e.getMessage(), e); } HashSet<Field> fields = new HashSet<Field>(); 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 = unsafe.objectFieldOffset(f); if (offset > maxSize) { maxSize = offset; } } return ((maxSize / 8) + 1) * 8; // padding }
大數組
-
java數組的最大大小是Integer.MAX_VALUE。使用直接內存分配,我們可以創建大小僅受堆大小限制的數組。
//java.lang.OutOfMemoryError: Requested array size exceeds VM limit int[] a = new int[Integer.MAX_VALUE]; //java.lang.OutOfMemoryError: Java heap space int[] b = new int[Integer.MAX_VALUE - 8]; 數組需要8個字節來存儲大小,所以數組最大長度是Integer.MAX_VALUE - 8
-
實現大數組
public class SuperArray { public static final Unsafe UNSAFE = unsafe(); private final static int BYTE = 1; private long size; private long address; public SuperArray(long size) { this.size = size; //以這種方式分配的內存不在堆中,也不在GC的管理之下,因此需要使用Unsafe.freeMemory()進行處理。 //它還不執行任何邊界檢查,因此任何非法訪問都可能導致JVM崩潰。 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; } private static Unsafe unsafe() { try { Field singleoneInstanceField = Unsafe.class.getDeclaredField("theUnsafe"); singleoneInstanceField.setAccessible(true); return (Unsafe) singleoneInstanceField.get(null); } catch (Exception e) { throw new RuntimeException(e.getMessage(), e); } } public void free() { UNSAFE.freeMemory(address); } } public class SuperArrayTest { @Test public void testSuperArray() { long SUPER_SIZE = (long) Integer.MAX_VALUE * 2; SuperArray array = new SuperArray(SUPER_SIZE); // 4294967294 System.out.println("Array size:" + array.size()); int sum = 0; for (int i = 0; i < 100; i++) { array.set((long) Integer.MAX_VALUE + i, (byte) 3); sum += array.get((long) Integer.MAX_VALUE + i); } // 300 System.out.println("100個元素的和:" + sum); //釋放內存 array.free(); } }
多繼承
-
在Java中本來是沒有多繼承的。除非我們可以將任意的類型轉爲我們想要的任意類型。除非我們可以轉換任意類型到任意其他類型。
long intClassAddress = normalize(getUnsafe().getInt(new Integer(0),4L)); long strClassAddress = normalize(getUnsafe().getInt("",4L)); getUnsafe().putAddress(intClassAddress+36,strClassAddress);
這段代碼添加
String
類成爲Integer
的父類,所以,我們可以直接轉換,而不會有運行時異常。(String)(Object)(new Integer(666))
動態class
-
我們可以在運行時創建類,例如讀取編譯好的 .class 文件。然後執行讀 class 內容到 byte 數組,並且傳遞給
defineClass
方法。byte[] classContents = getClassContent(); Class c = getUnsafe().defineClass(null,classContents,0,classContents.length); c.getMethod("a").invoke(c.newInstance(),null);// 1 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; }