java基礎之Unsafe

簡介

  1. 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;
    }
    
  2. 代碼變爲受信任的的方式

    1. 增加JVM虛擬機選項
    java -Xbootclasspath:/usr/jdk1.7.0/jre/lib/rt.jar:  ${path}   // 其中path爲調用Unsafe相關方法的類所在jar包路徑 
        
    
  3. 通過反射獲取Unsafe

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

方法

  1. Unsafe大部分方法都是native方法,分爲幾類

  2. 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());
    
  3. 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));
    }
    
  4. 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);
    
  5. Array:數組操作方法

    //返回數組中第一個元素的偏移地址
    public native int arrayBaseOffset(Class<?> arrayClass);
    //返回數組中一個元素佔用的大小
    public native int arrayIndexScale(Class<?> arrayClass);
    
  6. 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);
    
  7. 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

  1. 如果要跳過對象初始化或者繞過構造函數的安全檢查或者類沒有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());
    }
    

拋出異常

  1. 如果不喜歡Checked Exception,可以使用如下方式,此方法拋出檢查異常,但是代碼沒有被強制捕獲或重新拋出它,就像運行時異常一樣

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

SizeOf

  1. 使用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
    }
    

大數組

  1. 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
    
  2. 實現大數組

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

多繼承

  1. 在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

  1. 我們可以在運行時創建類,例如讀取編譯好的 .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;
    }
    
發佈了137 篇原創文章 · 獲贊 22 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章