如何获取一个对象的内存地址
获取一个对象的内存地址要比获取类的有趣多了。下面我们用一个java.lang.Object类型的对象数组来帮助实现这个目标。这个数组的长度只有1。
实现的步骤如下:
- 把目标对象(想要获取其地址的对象)作为刚刚提到的那个数组——helper array——的第一个元素(当然也是唯一的元素)。显然目标对象可以放在数组的索引为0的地方,因为数组的类型是Object(只要非基型元素都可以放下)。
- 然后,得到helper array的开始位移。一个数组的开始位移指的是指向数组第一个元素的指针在数组对象中的位移。(这里有点绕,但是想一下,一个数组对象并不是本身就是那个数组,而是包含指向数组的指针的对象元素,而这里要找的就是那个指针在数组对象中的位移)。Java中数组对象与数组关系如下图所示(如有错误请指正):
- 我们需要检查JVM地址的大小:
- 如果JVM是32位的,那么用sun.misc.Unsafe类从<数组地址> + <位移>的位置取出的4字节整型值就是目标对象的地址。
- 如果JVM是64位的,那么从<数组地址> + <位移> 的位置读取的一个long型值就是目标对象的地址。
For 32 bit JVM
Object helperArray[] = new Object[1];
helperArray[0] = targetObject;
long baseOffset = unsafe.arrayBaseOffset(Object[].class);
int addressOfObject = unsafe.getInt(helperArray, baseOffset);
For 64 bit JVM
Object helperArray[] = new Object[1];
helperArray[0] = targetObject;
long baseOffset = unsafe.arrayBaseOffset(Object[].class);
long addressOfObject = unsafe.getLong(helperArray, baseOffset);
在这里,你可以把targetObject当成是SampleClass的一个实例。当然,目标对象可以是任意类的对象。
类的内存布局(Class Memory Layout)
32-bit JVM
[header ] 4 byte
[klass pointer ] 4 byte (pointer)
[C++ vtbl ptr ] 4 byte (pointer)
[layout_helper ] 4 byte
[super check offset ] 4 byte
[name ] 4 byte (pointer)
[secondary super cache ] 4 byte (pointer)
[secondary supers ] 4 byte (pointer)
[primary supers ] 32 byte (8 length array of pointer)
[java mirror ] 4 byte (pointer)
[super ] 4 byte (pointer)
[first subklass ] 4 byte (pointer)
[next sibling ] 4 byte (pointer)
[modifier flags ] 4 byte
[access flags ] 4 byte
64-bit JVM
[header ] 8 byte
[klass pointer ] 8 byte (4 byte for compressed-oops)
[C++ vtbl ptr ] 8 byte (4 byte for compressed-oops)
[layout_helper ] 4 byte
[super check offset ] 4 byte
[name ] 8 byte (4 byte for compressed-oops)
[secondary super cache ] 8 byte (4 byte for compressed-oops)
[secondary supers ] 8 byte (4 byte for compressed-oops)
[primary supers ] 64 byte (32 byte for compressed-oops)
{8 length array of pointer}
[java mirror ] 8 byte (4 byte for compressed-oops)
[super ] 8 byte (4 byte for compressed-oops)
[first subklass ] 8 byte (4 byte for compressed-oops)
[next sibling ] 8 byte (4 byte for compressed-oops)
[modifier flags ] 4 byte
[access flags ] 4 byte
详情请见:
http://hg.openjdk.java.net/jdk7/jdk7/hotspot/file/9b0ca45cd756/src/share/vm/oops/klass.hpp
下图是SampleClass在32位JVM中的内存布局。图中列出了从起始地址开始的128个字节。
并且SampleBaseClass在32位JVM中的内存布局的头128个字节如下图所示:
接下来,我们着重介绍下其中一些重要的域(field)。下面我们介绍下图中用颜色标注的域。
header(红色)总是一个常量值“0x00000001”.
klass pointer(深蓝)是指向内存中java.lang.Class类的指针(所以在两个图中都是0x389708a8)。它表明当前的内存结构是一个类。
layout helper(橙色)是表明当前类结构的大小(shallow size,感兴趣的朋友可以查一下shallow size与retained size的区别)。这个大小不是以字节为单位的,而是总字节数除以JVM的域布局的边界对齐大小得出的值。在我们的环境中,对象是以8字节边界对齐的。layout helper的值为0x10,也就是16,所以,本例中类结构的大小就是16*8=128(字节)。
super(紫色)是一个指向父类所在内存空间的指针。在我们的例子中,SampleBaseClass是SampleClass的父类。在SampleClass中,其super域中的值为0x34104b70,也就是SampleBaseClass在内存中的定义地址。在SampleBaseClass中,这个域的值为0x38970000,也就是java.lang.Object类的地址。这是因为在Java中任何一个类都是Object类的子类。
修改标记(modifier flags, 青色)代表的是一系列Java类的标记。这些标记可以是”public”,”protected”,”private”,”abstract”,”static”,”final”和“strictfp”等。这个域中的值是通过按位的“或”操作计算得出的。我们的例子中,SampleClass这个类是pulic和final的(SampleClass类定义见系列文章2),因此其标记域的值就是“0x00000001 | 0x00000010 = 0x00000011 “。而SampleBaseClass只是”public”的,所以其此域值为“0x00000001”。这个域的取值如下:
modifiers | values |
---|---|
public | 0x00000001 |
protected | 0x00000002 |
private | 0x00000004 |
abstract | 0x00000400 |
static | 0x00000008 |
final | 0x00000010 |
strict | 0x00000800 |