前言

越发内卷的行业,使得程序员不得不去了解更多底层设计的东西,所以知识必须适可而止地总结一丢丢。

Java 对象的组成

Java 对象由三部分区域组成,分别是

  • 对象头
  • 实例数据
  • 对齐填充

实例数据

实例数据是,对象真正存储数据区域,包含了各种类型的字段。

例如 boolean 类型的字段站 1 字节,int 类型字段占 4 字节。long 类型字段占 8 字节。

对齐填充

HotSpot JVM 对内存的管理规范中,对象的大小必须是 8 字节的整倍数,所以当对象头 + 实例数据正好是 8 字节整倍数时,对齐填充就为 0。

如果不等于 8 字节整倍数,则需要补充字节占位,直到成为 8 字节整倍数。

对象头

Java 对象中对象头是最为复杂的一个区域,在 32 位 JVM 中占 8 字节,在 64 位 JVM 中占 16 字节,如果开启了压缩,则是占 12 字节。

对象头又包含两部分,mark word 、klass pointer。

http://openjdk.java.net/groups/hotspot/docs/HotSpotGlossary.html

mark word
The first word of every object header.
Usually a set of bitfields including synchronization state
and identity hash code. May also be a pointer
(with characteristic low bit encoding) to synchronization
related information. During GC, may contain GC state bits.

klass pointer
The second word of every object header. 
Points to another object (a metaobject) which describes
the layout and behavior of the original object. 
For Java objects, the "klass" contains a C++ style "vtable".

mark word:存放对象运行时的一些状态信息,gc 年龄、锁状态、hashCode 等。

klass pointer:指向对象类元数据的指针,虚拟机可以通过这个指针来确定对象是什么类型。

https://gist.github.com/arturmkrtchyan/43d6135e8a15798cc46c

32 位 JVM 对象头组成

20210320213515.png

64 位 JVM 对象头组成

20210320213602.png

64 位 JVM 对象头组成(开启压缩)

20210320213629.png

64 位 JVM 中,Mark Word 占 8 字节。

  • biased_lock,1 bit 用来表示 是否是偏向锁
  • lock,2 bit 表示 锁状态
  • biased_lock + lock ,共 3 位共同来标识锁状态和 gc 标志,如下枚举。

open jdk 源码中对 mark word 的定义
https://github.com/unofficial-openjdk/openjdk/blob/jdk8u/jdk8u/hotspot/src/share/vm/oops/markOop.hpp


enum { locked_value               = 0, //  0 00 轻量级锁
         unlocked_value           = 1, //  0 01 无锁
         monitor_value            = 2, //  0 10 重量级锁
         marked_value             = 3, //  0 11 gc 标志
         biased_lock_pattern      = 5 // 1 01 偏向锁
 };

在对象处于不同状态时,Mark Word 前 61 位也会动态变化。

例如当 Synchronized 锁升级过程中,偏向锁 是 54 bit 来保存持有锁线程的地址,2 bit 来保存 epoch 值。

轻量级锁 则用 62 bit 来保存持有锁线程栈帧中 lockRecord 区的地址。而 重量级锁 则用 62 bit 保存了指向 monitor 对象的地址。

20210320223659.jpg

Mark Word 中使用 4 bit 来代表 对象的年龄,由此可见年龄的范围在 0-15,所以年龄最大只能到 15,还存活的对象将从 S 区被移至老年代。

可视化对象大小

引入 org.openjdk.jol 包,可以打印出来对象的组成。


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

新建一个测试类,来打印对象的组成。


package com.lyqiang.sync.test;
import org.openjdk.jol.info.ClassLayout;

public class Test {
    public static void main(String[] args) {
        ObjA objA = new ObjA();
        System.out.println(ClassLayout.parseInstance(objA).toPrintable());
    }
}

class ObjA {
    boolean b;
    int i;
}

输出结果如下:

20210320230940.png

可以看出,对象头 object header 占了 12 字节,实例数据中 int 类型占 4 字节,boolean 类型占 1 字节,共 5字节。对象头加实例数据供 17 字节,所以补了 7 字节,凑成 8 的整数倍 24 字节。

总结

了解对象的组成,是认识对象大小的计算和 Synchronized 原理的必学一课。