Java对象的内存布局

HotSpot虚拟机中,对象在内存中存储的布局可以分为三块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。
image.png

对象头

对象头主要包括两部分 Mark Word(对象标记字段)和Class Pointer(Class对象的类型指针),
如果是数组对象,还包括数组长度。在64位的HotSpot虚拟机下,Mark Word占8个字节,其记录了Hash Code、GC信息、锁信息等相关信息;而Class对象指针则指向该实例的Class对象,在开启指针压缩的情况下占用4个字节,否则占8个字节;如果其是一个数组对象,则还需要4个字节用于记录数组长度信息。
下图是64位MarkWord的具体含义,左侧是高字节,右侧是低字节。
image.png

实例数据

实例数据就是对象实际存放的数据,大小由所有的成员变量决定,不包括静态变量。
在开启压缩指针的情况下,引用类型是占4个字节,如果关闭对象指针压缩就是占8个字节。
基本数据类型所占的字节数如下:
byte 1字节
short 2字节
int 4字节
long 8字节
char 2字节
float 4字节
double 8字节
boolean (1/4字节,具体取决于jvm实现)

内存填充

最后这段空间并非必须,仅仅为了起到占位符的作用。64位HotSpot虚拟机要求对象按8个字节对齐,所以每个对象的字节数是8的整数倍。、

指针压缩

64位的HotSpot虚拟机下,类型指针、引用类型需要占8个字节。从JDK 1.6开始,64位的JVM支持UseCompressedOops选项。其可对OOP(Ordinary Object Pointer,普通对象指针)进行压缩,使其只占用4个字节,以达到节约内存的目的。在JDK 8下,该选项默认启用。当然也可以通过添加JVM参数来显式进行配置

  1. -XX:+UseCompressedOops // 开启指针压缩
  2. -XX:-UseCompressedOops // 关闭指针压缩

利用JOL工具观测JAVA内存布局

  1. 首先加入POM依赖

    1. <!-- JOL依赖 -->
    2. <dependency>
    3. <groupId>org.openjdk.jol</groupId>
    4. <artifactId>jol-core</artifactId>
    5. <version>0.9</version>
    6. </dependency>
  2. 编写一个简单的Java类用于后续分析

    1. @Data
    2. public class Car {
    3. public static int NO = 1;
    4. public static String S = "STR";
    5. private int id;
    6. private String type;
    7. private double price;
    8. private char level;
    9. }
  3. 编写测试类,这里我的JDK1.8默认是开启指针压缩的。

    1. public class Test {
    2. public static void main(String[] args) {
    3. Object obj = new Object();
    4. System.out.println(ClassLayout.parseInstance(obj).toPrintable());
    5. Car car = new Car();
    6. System.out.println(ClassLayout.parseInstance(car).toPrintable());
    7. int[] array = new int[3];
    8. array[0] = 1;
    9. array[1] = 2;
    10. System.out.println(ClassLayout.parseInstance(array).toPrintable());
    11. }
    12. }

    我们看一下输出结果: ```java java.lang.Object object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

Car object internals: 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) d0 df 00 f8 (11010000 11011111 00000000 11111000) (-134160432) 12 4 int Car.id 0 16 8 double Car.price 0.0 24 2 char Car.level
26 2 (alignment/padding gap)
28 4 java.lang.String Car.type null Instance size: 32 bytes Space losses: 2 bytes internal + 0 bytes external = 2 bytes total

[I object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 6d 01 00 f8 (01101101 00000001 00000000 11111000) (-134217363) 12 4 (object header) 03 00 00 00 (00000011 00000000 00000000 00000000) (3) 16 12 int [I. N/A 28 4 (loss due to the next object alignment) Instance size: 32 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total ``` 看下开启指针压缩后对象分别所占的大小,分析一下:

  1. new Object()所占的字节数

可以看到输出的第一行和第二行是markword占8个字节,
第三行是class pointer占4个字节,没有实例数据所以是0个字节。
总共是8 + 4 = 12字节,因为不是8的整数倍,所以最后填充4个字节对齐,总共占用16个字节。

  1. new Car()所占的字节数

同样对象头markword和class pointer占用8 + 4 = 12字节,静态变量不占用对象内存字节,
成员变量int id占用4字节,double price占用8字节,char level占用2字节,String type占用4字
节,实例数据共占用4 + 8 + 2 + 4 = 18字节。加上对象头 18 + 12 = 30 字节。
最后因为不是8的整数倍,填充2字节,30 + 2 = 32 字节。

  1. new int[3]所占的字节数

对象头多了一个数组长度占用4字节,
对象头一共是:8(mark word)+ 4(class pointer)+ 4 (array length)= 16字节。
数组长度是3,类型是int,所以实例数据共占有 4 * 3 = 12字节。
上述一共 16 + 12 = 28 字节,字节填充4字节成为8的整数倍,一共是28 + 4 = 32字节。

参考文章

https://zhuanlan.zhihu.com/p/151856103