Java 对象与 JVM | 青训营笔记

111 阅读2分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的第 4 篇笔记

Java 对象内存存储布局

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

Java对象内存存储布局.png

  • MarkWord:标记存储对象本身运行时的数据,包含哈希值(指向对象在堆中的首地址值)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。在32位系统占4字节,在64位系统中占8字节
  • ClassPointer:类型指针,存储的是对象指向它的Class对象(其对应的类元数据对象)的首地址。在开启指针压缩的情况下,占 4 字节,未开启情况下,占 8 字节
  • Length:只在数组对象中存在,用来记录数组的长度,占用4字节
  • Instance Data:对象实例数据,存储本类对象的成员变量和所有可见的父类成员变量。(这里不包括静态成员变量,因为其是在方法区维护的)
  • Padding:Java对象的存储空间分配单位是8个字节,不足8字节,也会分配8字节进行填充。即所有Java对象占用bytes数必须是8的倍数,padding的作用就是补充字节,保证对象是8字节的整数倍。64位计算机上Java对象头的大小刚好是8的2倍,因此当实例数据部分没有对齐时,就需要通过对齐填充来补全

可以通过 JOL 工具打印对象布局

添加依赖:

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

使用示例:

public class Main {
    public static void main(String[] args) {
        Object obj = new Object();
        System.out.println(ClassLayout.parseInstance(obj).toPrintable());
    }
}

指针压缩

-XX:+UseCompressedClassPointers:压缩对象头类指针

-XX:+UseCompressedOops:压缩对象引用指针

32 位 JRE 的指针是 8 位(32 bit),无需压缩;64 位 JRE 开启指针压缩都是将指针大小从 8 个字节压缩为 4 个字节。

UseCompressedClassPointers依赖于UseCompressedOops,即UseCompressedClassPointers的开启要求UseCompressedOops也开启才生效。

// 这行代码在 JVM 到底占用多少个字节?
Object o = new Object()

无论有没有开启指针压缩,new Object() 都占 16 个字节。

如果考虑变量 o 这个指针的大写,在压缩指针的情况下,是 4 个字节 + 16 个字节 = 20 个字节, 在不压缩指针的情况下,是 8 个字节 + 16 个字节 = 24 个字节。

请问下面代码的输出结果

public class Main {

    static class Wrapper {
        int size;
        Integer state;
        String name;
    }

    public static void main(String[] args) {
        Object obj = new Wrapper();
        System.out.println(ClassLayout.parseInstance(obj).toPrintable());
    }
}

答案:开启指针压缩,24 个字节;不开启指针压缩,40 个字节(4 个字节是填充)。