Unsafe API介紹及其使用


廢話

  個人理解:java 出現的原因之一,就是對內存的管理;在c/c++,內存可以隨心使用,超高的性能也伴有極高的風險;java極大的規避了這種風險,卻也降低了程序運行的性能;那么java是否提供直接操作內存的方法呢?當然:Unsafe 類就是java提供的,對系統硬件級別的底層操作;

1,Unsafe 的獲取方法:

  Unsafe 位於sun.misc包下,通常eclipse限制了對該類的直接使用,並且也不能通過Unsafe提供的getUnsafe() 方法獲取到該類的實例,因為你的類不被該類所信任;具體到源碼:

@CallerSensitive
public static Unsafe getUnsafe() {
    Class var0 = Reflection.getCallerClass();
    if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
        throw new SecurityException("Unsafe");
    } else {
        return theUnsafe;
    }
}

  在方法上有一個@CallerSensitive注解,該注解表示該方法的調用,需要調用者被該方法信任;那么怎么獲取到Unsafe的實例呢?解決方法如下:

  利用反射機制 ,Unsafe中有一個字段名為“theUnsafe”,該字段保存有一個Unsafe的實例,只要獲取在該字段上的Unsafe實例就好了,代碼如下:

    @SuppressWarnings("restriction")
    static private sun.misc.Unsafe getUnsafe() throws IllegalArgumentException, IllegalAccessException {
        Class<?> cls = sun.misc.Unsafe.class;
        Field[] fields = cls.getDeclaredFields();
        for(Field f : fields) {
            if("theUnsafe".equals(f.getName())) {
                f.setAccessible(true);
                return (sun.misc.Unsafe) f.get(null);
            }
        }
        throw new IllegalAccessException("no declared field: theUnsafe");
    }

 

2,Unsafe 獲取對象字段偏移量,及修改偏移量對應字段的值,代碼如下:

import java.lang.reflect.Field;public class TestUnsafe {
    
    static private int number = 5;
    
    private String c;

    @SuppressWarnings({ "restriction" })
    public static void main(String[] args) throws Throwable {
        

        TestUnsafe t = new TestUnsafe();
        
        sun.misc.Unsafe unsafe = getUnsafe(); 

        
        
        //對象的操作
        //1,獲取對象的字段相對該對象地址的偏移量;
        
        //1.1 靜態字段獲取 ;說明:靜態字段的偏移量相對於該類的內存地址,即相對於 className.class 返回的對象;
        long staticFieldOffset = unsafe.staticFieldOffset(TestUnsafe.class.getDeclaredField("number"));
//1.2 非靜態字段 ;說明:該偏移量相對於該類的實例化對象的內存地址,即 new 返回的對象; 這里相對於上面實例化的 t對象 long unstaticFieldOffset = unsafe.objectFieldOffset(TestUnsafe.class.getDeclaredField("c")); System.out.println("靜態變量相對於類內存地址的偏移量 = " + staticFieldOffset); System.out.println("非靜態變量相對於實例化對象的偏移量 = " + unstaticFieldOffset); //修改對象字段的值; //1.3 修改非基本數據類型的值,使用:putObject(object , offset , value); 這里修改 實例化對象t對應偏移地址字段的值; unsafe.putObject(t, unstaticFieldOffset, "b"); //1.3 修改基本數據類型的值,使用對應類型的put方法,如:int 使用 putInt(object , offset , value); unsafe.putInt(TestUnsafe.class, staticFieldOffset, 4); System.out.println("靜態變量被修改后的值 = " + TestUnsafe.number); System.out.println("非靜態變量被修改后的值 = " + t.c); }
   //利用反射獲取Unsafe的實例 @SuppressWarnings(
"restriction") static private sun.misc.Unsafe getUnsafe() throws IllegalArgumentException, IllegalAccessException { Class<?> cls = sun.misc.Unsafe.class; Field[] fields = cls.getDeclaredFields(); for(Field f : fields) { if("theUnsafe".equals(f.getName())) { f.setAccessible(true); return (sun.misc.Unsafe) f.get(null); } } throw new IllegalAccessException("no declared field: theUnsafe"); } }

 

3,Unsafe 內存的使用:申請allocateMemory(long)、擴展reallocateMemory(long,long)、銷毀freeMemory(long)、插入值putXXX()、獲取值getXXX(),示例代碼如下:

        //內存使用 
        //說明:該內存的使用將直接脫離jvm,gc將無法管理以下方式申請的內存,以用於一定要手動釋放內存,避免內存溢出;
        //2.1 向本地系統申請一塊內存地址; 使用方法allocateMemory(long capacity) ,該方法將返回內存地址的起始地址
        long address = unsafe.allocateMemory(8);
        System.out.println("allocate memory address = " + address);
        
        //2.2  向內存地址中設置值;
        //2.2 說明: 基本數據類型的值的添加,使用對應put數據類型方法,如:添加byte類型的值,使用:putByte(內存地址 , 值);
        unsafe.putByte(address, (byte)1);
        
        //2.2 添加非基本數據類型的值,使用putObject(值類型的類類型 , 內存地址 , 值對象);
        unsafe.putObject(Hello.class, address+2, new Hello());
        
        //2.3 從給定的內存地址中取出值, 同存入方法基本類似,基本數據類型使用getXX(地址) ,object類型使用getObject(類類型,地址);
        byte b = unsafe.getByte(address);
        System.out.println(b);
        
        //2.3 獲取object類型值
        Hello h = (Hello) unsafe.getObject(Hello.class, address+2);
        System.out.println(h);
        
        //2.4 重新分配內存 reallocateMemory(內存地址 ,大小) , 該方法說明 :該方法將釋放掉給定內存地址所使用的內存,並重新申請給定大小的內存;
        // 注意: 會釋放掉原有內存地址 ,但已經獲取並保存的值任然可使用,原因:個人理解:使用unsafe.getXXX方法獲取的是該內存地址的值,
        //並把值賦值給左邊對象,這個過程相當於是一個copy過程--- 將系統內存的值 copy 到jvm 管理的內存中;
        long newAddress = unsafe.reallocateMemory(address, 32);
        System.out.println("new address = "+ newAddress);
        //再次調用,內存地址的值已丟失; 被保持與jvm中的對象值不被丟失;
        System.out.println("local memory value =" + unsafe.getByte(address) + " jvm memory value = "+ b);
        
        //2.5 使用申請過的內存;
        //說明: 該方法同reallocateMemory 釋放內存的原理一般;
        unsafe.freeMemory(newAddress);
        
        //2.5 put 方法額外說明
        //putXXX() 方法中存在於這樣的重載: putXXX(XXX ,long , XXX) ,如:putInt(Integer ,long , Integer) 或者 putObject(Object ,long ,Object)
        //個人理解 : 第一個參數相當於作用域,即:第三個參數所代表的值,將被存儲在該域下的給定內存地址中;(此處疑惑:
        //如果unsafe是從操作系統中直接獲取的內存地址,那么該地址應該唯一,重復在該地址存儲數據,后者應該覆蓋前者,但是並沒有;應該是jvm有特殊處理,暫未研究深入,所以暫時理解為域;)
        //以下示例可以說明,使用allocateMemory申請的同一地址,並插入不同對象所表示的值,后面插入的值並沒有覆蓋前面插入的值;
        //
        long taddress = unsafe.allocateMemory(1);
        Hello l = new Hello("l");
        Hello l1 = new Hello("l1");
        unsafe.putObject(l, taddress, l);
        System.out.println(unsafe.getObject(l, taddress));
        unsafe.putObject(l1, taddress, l1);
        System.out.println(unsafe.getObject(l1, taddress));
        System.out.println(unsafe.getObject(l, taddress));
        unsafe.putObject(Hello.class, taddress, new Hello("33"));
        System.out.println(unsafe.getObject(Hello.class, taddress));
        
        unsafe.freeMemory(taddress);

  重要的事情說n遍::::Unsafe申請的內存的使用將直接脫離jvm,gc將無法管理Unsafe申請的內存,所以使用之后一定要手動釋放內存,避免內存溢出!!!

 

4,CAS 操作(CAS,compare and swap的縮寫,意:比較和交換):硬件級別的原子性更新變量;在Unsafe 中主要有三個方法:CompareAndSwapInt() ,CompareAndSwapLong() ,CompareAndSwapObject();具體操作,代碼如下:

//3.0關於並發對變量的原子操作,請查看其它資料;unsafe 提供硬件級別的原子操作CAS方法,如:compareAndSwapInt(Object ,long ,int ,int)
        //說明: 第一個參數:需要更新的對象;第二個參數:偏移地址; 第三個對象:預期在該偏移地址上的當前值,即:getInt(obj,偏移地址) == 預期值; 第四個參數:需要更新的值
        //此類方法,當且僅當當前偏移量的值等於預期值時,才更新為給定值;否則不做任何改變;
        //compareAndSwapObject 和 compareAndSwapLong 與下述示例類似;
        long offset = unsafe.allocateMemory(1);
unsafe.putInt(Integer.
class, offset, 1);
System.out.println(unsafe.getInt(Integer.
class, offset));
boolean updateState = unsafe.compareAndSwapInt(Integer.class, offset, 1, 5); System.out.println("update state = "+ updateState +" ; value = " + unsafe.getInt(Integer.class,offset)); unsafe.freeMemory(offset);

 

5,線程掛起和恢復,part()、unpart(),代碼如下:

        //4.1  unsafe提供線程掛起和恢復的原語;
        /*    掛起線程,方法如下
         *  part(boolean abs,long timeout) 
         *  方法說明:將當前線程掛起,直到當期時間“到達”(1)timeout描述的時間點,或者等待線程中斷或unpark;
         *  (1):注意:這里使用的是到達,即給定的timeout時間是一個時間點,該時間點從1970計數開始;
         *  參數說明:
         *      abs 為false 時,表示timeout以納秒為單位 ;當為false是,可設置timeout為0,表示永遠掛起,直到interrupt 或則 unpart
         *      abs 為true 時,表示timeout以毫秒為單位;注意,經測試在abs為true時,將timeout設置為0,線程會立即返回;
         *      timeout : 指定線程掛起到某個時間點,該時間點從1970計數開始;
         */
        //ex1 :
        Thread thread = new Thread(()->{
            unsafe.park(false, 0);//永遠掛起
        });
        thread.start();
        
        /*
         * 4.2 恢復線程,方法如下:
         * unpark(Object thread);
         * 方法說明: 給與傳入對象一個運行的許可,即將給定的線程從掛起狀態恢復到運行狀態;
         * 參數說明:thread :通常是一個線程對象;
         * 特殊說明:unpark 可以在park之前使用,但不論在park方法之前,進行了多少次的調用unpark方法,對於作為參數的thread線程始終將只獲得一個運行許可;
         * 即:當park方法調用時,檢測到該線程存在一個運行許可,park方法也會立即返回;這種方式在多線程中雖然很靈活,相對於notify/wait的方式,但不建議如此使用;
         */
        //ex2:
        unsafe.unpark(thread);//恢復線程

 


免責聲明!

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



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