ava被设计成一个安全,可管理的环境,然而 Java HotSpot有一个后门,提供了对低级别的,对直接内存和线程的操作。这个后门是—-sun.misc.Unsafe。这个类在JDK中有广泛的应用,例如,java.nio和java.util.concurrent。很难想象在日常开发中使用这些危险的,不可移植和未经校验的API。然而,Unsafe提供一种简单的方法来观察HotSpot JVM内部的一些技巧。
sun.misc.Unsafe这个类的访问是受限的,它的构造方法是私有的,相应的工厂方法要求必须被Bootloader载入才能使用,也就是说,只有JDK内部分才能使用这个工厂方法来构造Unsafe对象。
1
2
3
4
5
6
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()函数的一种包装。
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将会在这样的内存欺骗之后发生。
想象一下有一个以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/ | 逍遥冲