JOL查看对象内存结构

5,126 阅读6分钟

jol是一款查看java对象内存结构的工具,第一次使用时着实踩了不少坑。 jol的maven依赖如下:

        <dependency>
            <groupId>org.openjdk.jol</groupId>
            <artifactId>jol-core</artifactId>
            <version>0.9</version>
        </dependency>

测试类代码:

public class ObjectHeadTest {
	private int intValue = 0;
    public static void main(String[] args) {
            ObjectHeadTest object = new ObjectHeadTest();
            //打印hashcode
            System.out.println(object.hashCode());
            //查看字节序
            System.out.println(ByteOrder.nativeOrder());

            //打印当前jvm信息
            System.out.println(VM.current().details());
            String classLayout = ClassLayout.parseInstance(object).toPrintable();
            System.out.println(classLayout);
        }
}

上面的代码直接执行得到如下结果:

2034688500
LITTLE_ENDIAN
# Running 64-bit HotSpot VM.
# Using compressed oop with 3-bit shift.
# Using compressed klass with 0x0000000800000000 base address and 0-bit shift.
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]

jdk_test.ObjectHeadTest object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 f4 e1 46 (00000001 11110100 11100001 01000110) (1189213185)
      4     4        (object header)                           79 00 00 00 (01111001 00000000 00000000 00000000) (121)
      8     4        (object header)                           40 70 06 00 (01000000 01110000 00000110 00000000) (421952)
     12     4    int ObjectHeadTest.intValue                   0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

可以看到ObjectHeadTest实例的header只有12个字节。而64位的jvm对象的header应该占16个字节128位。造成这种情况的原因是jvm默认开启了指针压缩,在64位vm上会把64位的对象指针压缩成32位(原因和细节这里暂不延伸 有兴趣可以自行搜索下) 。这就导致原本应该占8byte的klass对象指针只占了4byte。使用-XX:-UseCompressedOops参数可以关闭指针压缩。开启和关闭指针压缩的头部信息如下两张表所示:

开启指针压缩:

-------------------------------------------------------------------------------------------------------------|--------------------|
|                                            Object Header (96 bits)                                           |        State       |
|--------------------------------------------------------------------------------|-----------------------------|--------------------|
|                                  Mark Word (64 bits)                           |    Klass Word (32 bits)     |                    |
|--------------------------------------------------------------------------------|-----------------------------|--------------------|
| unused:25 | identity_hashcode:31 | cms_free:1 | age:4 | biased_lock:1 | lock:2 |    OOP to metadata object   |       Normal       |
|--------------------------------------------------------------------------------|-----------------------------|--------------------|
| thread:54 |       epoch:2        | cms_free:1 | age:4 | biased_lock:1 | lock:2 |    OOP to metadata object   |       Biased       |
|--------------------------------------------------------------------------------|-----------------------------|--------------------|
|                         ptr_to_lock_record                            | lock:2 |    OOP to metadata object   | Lightweight Locked |
|--------------------------------------------------------------------------------|-----------------------------|--------------------|
|                     ptr_to_heavyweight_monitor                        | lock:2 |    OOP to metadata object   | Heavyweight Locked |
|--------------------------------------------------------------------------------|-----------------------------|--------------------|
|                                                                       | lock:2 |    OOP to metadata object   |    Marked for GC   |
|--------------------------------------------------------------------------------|-----------------------------|--------------------|

关闭指针压缩

|------------------------------------------------------------------------------------------------------------|--------------------|
|                                            Object Header (128 bits)                                        |        State       |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
|                                  Mark Word (64 bits)                         |    Klass Word (64 bits)     |                    |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
| unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 |    OOP to metadata object   |       Normal       |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
| thread:54 |       epoch:2        | unused:1 | age:4 | biased_lock:1 | lock:2 |    OOP to metadata object   |       Biased       |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
|                       ptr_to_lock_record:62                         | lock:2 |    OOP to metadata object   | Lightweight Locked |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
|                     ptr_to_heavyweight_monitor:62                   | lock:2 |    OOP to metadata object   | Heavyweight Locked |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
|                                                                     | lock:2 |    OOP to metadata object   |    Marked for GC   |
|------------------------------------------------------------------------------|-----------------------------|--------------------|

Object Header form thanks for gist.github.com/arturmkrtch…

还有一个我现在都不太明白的踩坑点 在上面我调用了一遍object.hashCode()来查看hash码,如果不手动调用一遍instance的hashCode方法 classLayout打印出来的hashcode就是空的

 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           40 70 06 00 (01000000 01110000 00000110 00000000) (421952)
     12     4    int ObjectHeadTest.intValue                   0
     20     4        (loss due to the next object alignment)

System.out.println(ByteOrder.nativeOrder()); 可以查看当前cpu的字节序。输出是LITTLE_ENDIAN意味着是小端序。

  • 小端序:数据的高位字节存放在地址的高端 低位字节存放在地址低端
  • 大端序: 数据的高位字节存放在地址的低端 低位字节存放在地址高端 比如一个整形0x1234567 ,1是高位数据,7是低位数据。按照小端序01放在内存地址的高位,比如放在0x100 ,23就放在0x101以此类推。大端序反之。
    字节序
    因为堆的地址序是从低到高的所以大端序更符合人类的阅读习惯。而大部分intel和amd的cpu都是使用的小端序。关闭指针压缩的输出如下:
jdk_test.ObjectHeadTest object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           09 f4 e1 46 (00001001 11110100 11100001 01000110) (1189213193)
      4     4        (object header)                           79 00 00 00 (01111001 00000000 00000000 00000000) (121)
      8     4        (object header)                           18 55 a0 ab (00011000 01010101 10100000 10101011) (-1415555816)
     12     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
     16     4    int ObjectHeadTest.intValue                   0
     20     4        (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

前8位是markword,后8位是Klass指针。先不管Klass指针,我们把markword转成大端序便于查看

#markword with big endian
00000000 00000000 00000000 01111001 01000110 11100001 11110100 00001001

前25位是未使用的,之后的31位是hashcode 001111001010001101110000111110100(第一个0为符号位)转成十进制即为2034688500和hashCode()方法的值一致 最后的8位00001001第一个0没使用 后面的001对应age,biased_lock和lock。这三个字段的详细说明如下:

  • age - number of garbage collections the object has survived. It is incremented every time an object is copied within the young generation. When the age field reaches the value of max-tenuring-threshold, the object is promoted to the old generation.
  • biased_lock - contains 1 if the biased locking is enabled for the class. 0 if the biased locking is disabled for the class. To find out more on biased locking check out my previous post.
  • lock - the lock state of the object. 00 - Lightweight Locked, 01 - Unlocked or Biased, 10 - Heavyweight Locked, 11 - Marked for Garbage Collection. To find out more on locking/synchronization check out synchronization post.

age是对象经历的gc次数,biased_lock代表是否使用偏向锁,lock是锁的状态这里001表示没有使用锁

header占用12个字节,之后的4个字节是对象的int属性,之后又4个字节的对齐填充。

由于HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,也就是说就是对象的大小必须是8字节的整数倍。如上header加上fileld是20字节,所以需要4字节的对齐填充。