Java直接內存訪問的技巧
Feb262013php
做者:逍遙衝 發佈:2013-02-26 21:35 分類:JavaSE 閱讀:23,814 瀏覽數 1條評論 java
Java被設計成一個安全,可管理的環境,然而 Java HotSpot有一個後門,提供了對低級別的,對直接內存和線程的操做。這個後門是—-sun.misc.Unsafe。這個類在JDK中有普遍的應用,例如,java.nio和java.util.concurrent。很難想象在平常開發中使用這些危險的,不可移植和未經校驗的API。然而,Unsafe提供一種簡單的方法來觀察HotSpot JVM內部的一些技巧。安全
獲取Unsafe
sun.misc.Unsafe這個類的訪問是受限的,它的構造方法是私有的,相應的工廠方法要求必須被Bootloader載入才能使用,也就是說,只有JDK內部分才能使用這個工廠方法來構造Unsafe對象。數據結構
1wordpress 2函數 3工具 4學習 5測試 6spa 7 8 9 10 11 12 13 |
public final class Unsafe { ... private Unsafe() {} private static final Unsafe theUnsafe = new Unsafe(); ... public static Unsafe getUnsafe() { Class cc = sun.reflect.Reflection.getCallerClass( 2 ); if (cc.getClassLoader() != null ) throw new SecurityException( "Unsafe" ); return theUnsafe; } ... } |
幸運地是,有一個theUnsafe屬性能夠被利用來檢索Unsafe實例,咱們能夠見到的寫一個反射方法,來獲取Unsafe實例:
1 2 3 4 5 6 7 |
public static Unsafe getUnsafe() { try { Field f = Unsafe. class .getDeclaredField( "theUnsafe" ); f.setAccessible( true ); return (Unsafe)f.get( null ); } catch (Exception e) { /* ... */ } } |
下面將學習一些Unsafe的方法。
1.long getAddress(long address) 和void putAddress(long address, long x)
對直接內存進行讀寫。
2.int getInt(Object o, long offset) , void putInt(Object o, long offset, int x)
另外一個相似的方法對直接內存進行讀寫,將C語言的結構體和Java對象進行轉換。
3.long allocateMemory(long bytes)
這個能夠看作是C語言的malloc()函數的一種包裝。
sizeof()函數
Java對象的結構以下圖所示:

第一個技巧,是模擬C語言的sizefo()函數,這個函數返回對象的字節大小。咱們能夠用以下的代碼實現sizeof()函數:
1 2 3 4 5 6 7 8 9 |
public static long sizeOf(Object object) { Unsafe unsafe = getUnsafe(); return unsafe.getAddress( normalize( unsafe.getInt(object, 4L) ) + 12L ); } public static long normalize( int value) { if (value >= 0 ) return value; return (~0L >>> 32 ) & value; } |
咱們須要使用normalize()函數,由於若是內存地址若是在2^31和2^32之間,將會自動的覆蓋鄰近的整型,也就是說用補碼的方式進行存儲。讓咱們在32位JVM(JDK6或者7)中進行測試:
1 2 3 4 5 |
// 執行sizeOf(new MyStructure())獲得以下的結果: class MyStructure { } // 8: 4 (起始標記) + 4 (指向類的指針) class MyStructure { int x; } // 16: 4 (起始標記) + 4 (指向類的指針) + 4 (int) + 4 填充字節用來對齊64位塊 class MyStructure { int x; int y; } // 16: 4 (起始標記) + 4 (指向類的指針) + 2*4 |
直接內存管理
Unsafe容許經過allcateMemory和freeMemory方法對內存進行顯示的分配和回收,直接分配的內存不在GC的控制內,而且不受限於JVM堆的大小。一般,經過NIO的脫離堆約束的緩衝,這些方法是安全有效的,可是有趣的是這讓標準的Java引用映射非堆內存變成了可能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
MyStructure structure = new MyStructure(); // create a test object structure.x = 777 ; long size = sizeOf(structure); long offheapPointer = getUnsafe().allocateMemory(size); getUnsafe().copyMemory( structure, // source object 0 , // source offset is zero - copy an entire object null , // destination is specified by absolute address, so destination object is null offheapPointer, // destination address size ); // test object was copied to off-heap Pointer p = new Pointer(); // Pointer is just a handler that stores address of some object long pointerOffset = getUnsafe().objectFieldOffset(Pointer. class .getDeclaredField( "pointer" )); getUnsafe().putLong(p, pointerOffset, offheapPointer); // set pointer to off-heap copy of the test object structure.x = 222 ; // rewrite x value in the original object System.out.println( ((MyStructure)p.pointer).x ); // prints 777 .... class Pointer { Object pointer; } |
因此,事實上是能夠對真實對象進行內存分配和回收的,不僅僅只是NIO中的字節緩衝。固然,有一個比較大的問題是,GC將會在這樣的內存欺騙以後發生。
繼承自Final類和void*
想象一下有一個以String爲參數的方法,但它須要經行外部的重載。具體代碼以下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
Carrier carrier = new Carrier(); carrier.secret = 777 ; String message = (String)(Object)carrier; // ClassCastException handler( message ); ... void handler(String message) { System.out.println( ((Carrier)(Object)message).secret ); } ... class Carrier { int secret; } |
爲了讓這段代碼能工做,首先須要更改Carrier類去假裝成String的子類。superclasses列表被存儲在Carrier類結構體28的位置,如上文圖中所示。原則上,添加以下的代碼可讓Carrer轉化成String:
1 2 3 |
long carrierClassAddress = normalize( unsafe.getInt(carrier, 4L) ); long stringClassAddress = normalize( unsafe.getInt( "" , 4L) ); unsafe.putAddress(carrierClassAddress + 32 , stringClassAddress); // insert pointer to String class to the list of Carrier's superclasses |
這樣,類型轉化能夠正常工做。然而,這樣的轉換方式是不切當而且違反虛擬機規範的。更詳細的方法將包含以下的步驟:
1.在Carrier類中32的位置實際上包含了一個指向Carrier類本身的指針,因此這個指針將被轉移到36的位置上,不單單是被指針重寫到String類。
2.當Carrier繼承自String的時候,String類的final標記將被移掉。
結論
sun.misc.Unsafe提供了幾乎是不受限制的監控和修改虛擬機運行時數據結構的能力。儘管這些能力幾乎是和Java開發自己不相干的,可是對於想要學習HotSpot虛擬機可是沒有C++代碼調試,或者須要去建立特別的分析工具的人來講,Unsafe是一個偉大的工具。
本文固定連接:http://www.xiaoyaochong.net/wordpress/index.php/2013/02/26/java%e7%9b%b4%e6%8e%a5%e5%86%85%e5%ad%98%e8%ae%bf%e9%97%ae%e7%9a%84%e6%8a%80%e5%b7%a7/ | 逍遙衝