使用JOL工具查看java对象布局

2,585 阅读4分钟

这是我参与8月更文挑战的第8天,活动详情查看:8月更文挑战

JOL简介

JOL(Java对象布局)是用于分析JVM中对象布局方案的微型工具箱。这些工具大量使用UnsafeJVMTI可服务性代理(SA)来解码实际的对象布局、示意图和引用。

jol的官方文档openjdk.java.net/projects/co…

本文着重介绍怎么使用JOL查看对象的结构,关于对象的结构文章,可以参考juejin.cn/post/699330…

对象头的结构信息

image.png

使用JOL工具查看java对象布局

引入jol依赖

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

创建对象示例

Foo类中有一个整形变量i,一个长整形变量j和一个引用类型变量bar

  1. Bar
public class Bar {
    private int k;
}
  1. Foo
public class Foo {
    private int i;
    private long j;
    private Bar bar;
}

打印对象信息

打印没有HashCode的对象信息
@Test
public void test() {
    Foo foo = new Foo();
    System.out.println(ClassLayout.parseInstance(foo).toPrintable());
}

输出结果:

org.ywb.Foo object internals:
OFF  SZ          TYPE DESCRIPTION               VALUE
  0   8               (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4               (object header: class)    0xf8011ce4
 12   4           int Foo.i                     0
 16   8          long Foo.j                     0
 24   4   org.ywb.Bar Foo.bar                   null
 28   4               (object alignment gap)    
Instance size: 32 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
  1. 出对象头包含两部分,MarkWordClassPoint分别占用了8个字节和4个字节,因为没有任何同步代码块使用该对象作为锁,所以该对象处于一个无锁状态,即,偏向锁的标志位为0,锁标志位01

  2. 对象体此时有三个属性,分别是占用4个字节的int类型的i,占用8个字节的long类型变量j,和一个引用类型,长度为4。

java 数据类型所占字节数

image.png

  1. 因为JVM规定,对象头的大小必须是8字节的整数倍,因为 8 + 4 = 12,不够,所以需要额外的 4字节object alignment gap(对齐字节)进行填充。

  2. 对象初始化后,会自动调用<init>方法对对象属性进行赋值,int,long类型默认复制为0,引用类型默认赋值为null

  3. 对象总大小为32字节

打印带hashCode的对象结构信息

对象一旦生成了 hashcode,JVM 会将其记录在对象头的 Mark Word 中;

注意只有调用未重写的 Object.hashcode()方法,或者调用 System.IdentityHashCode(obj)方法时,其值才被记录到 Mark Word中;如果调用的是重新的 hashcode()方法,也不会记录到 Mark Word 中。

对象一旦生成了 hashcode,那么它就无法进入偏向锁状态;也就是说,只要一个对象已经计算过 hashcode,它就无法进入偏向锁状态;当一个对象当前正处于偏向锁状态,并且需要计算其 hashcode 的话,则它的偏向锁会被撤销,并且锁会膨胀为重量级锁。

@Test
public void test() {
    Foo foo = new Foo();
    int code = foo.hashCode();
    System.out.println(ClassLayout.parseInstance(foo).toPrintable());
}

输出结果:

org.ywb.Foo object internals:
OFF  SZ          TYPE DESCRIPTION               VALUE
  0   8               (object header: mark)     0x000000694f943101 (hash: 0x694f9431; age: 0)
  8   4               (object header: class)    0xf8011d21
 12   4           int Foo.i                     0
 16   8          long Foo.j                     0
 24   4   org.ywb.Bar Foo.bar                   null
 28   4               (object alignment gap)    
Instance size: 32 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

跟刚刚的没有太大区别,只是对象HashCode被存储在对象头中了。

数组对象结构信息
@Test
public void test() {
    Foo[] foos = new Foo[4];
    System.out.println(ClassLayout.parseInstance(foos).toPrintable());
}

输出结果:

[Lorg.ywb.Foo; object internals:
OFF  SZ          TYPE DESCRIPTION               VALUE
  0   8               (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4               (object header: class)    0xf8011d23
 12   4               (array length)            4
 12   4               (alignment/padding gap)   
 16  16   org.ywb.Foo Foo;.<elements>           N/A
Instance size: 32 bytes
Space losses: 4 bytes internal + 0 bytes external = 4 bytes total
  1. 对象头中增加了一个数组长度的属性,这个属性在一般对象中是没有的,只有类型为数组类型才具备。
  2. 对象体中只有一个属性,因为我们的数组申请的长度为4,一个引用的大小为4,所以该属性的大小为 4 * 4 = 16