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/ | 逍遥冲