Java对象的内存布局

Posted by xdshent on April 10, 2022

工具

JOL(Java Object Layout)

1
2
3
4
5
<dependency>
   <groupId>org.openjdk.jol</groupId>
   <artifactId>jol-core</artifactId>
   <version>0.9</version>
</dependency>
  • java version “11.0.2” 2019-01-15 LTS
  • macOS Monterey 12.3.1

开启/关闭指针压缩的内存布局

指针压缩64bit系统特有

开启指针压缩内存布局

use-compress-oops

详情

  • Mark Word
    • 4B: 32bit系统
    • 8B: 64bit系统
  • Class Pointer
    • 4B
  • 数组长度
    • 4B: 数组
    • 无此区域: 对象
  • Instance Data
    • boolean: 1B
    • byte: 1B
    • short: 2B
    • int: 4B
    • long: 8B
    • float: 4B
    • double: 8B
    • char: 2B
    • 引用类型: 4B
  • Padding
    • 所有对象大小都必须能被8整除(8的倍数)

关闭指针压缩内存布局

use-compress-oops

详情

  • Mark Word
    • 4B: 32bit系统
    • 8B: 64bit系统
  • Class Pointer
    • 8B
  • 数组长度
    • 4B: 数组
    • 无此区域: 对象
  • Instance Data
    • boolean: 1B
    • byte: 1B
    • short: 2B
    • int: 4B
    • long: 8B
    • float: 4B
    • double: 8B
    • char: 2B
    • 引用类型: 8B
  • Padding
    • 所有对象大小都必须能被8整除(8的倍数)

验证

前置知识

JVM分配内存空间一次最少分配8B,对象中字段对齐的最小粒度为4B

  • 对齐和补齐

    • 对齐

      • 任何对象都是以8B来对齐的
    • 补齐

      • 4B

空字段对象

1
2
3
4
5
6
7
public class EmptyObjectMemoryLayout {

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

开启指针压缩

默认或者加上-XX:+UseCompressedOops

use-compress-empty-object

对象大小 = Mark Word + 类型指针 + 数组长度 + 实例数据(字段4字节补齐) + 对齐填充

16B = (4 + 4) + 4 + 0 + 0 + 4(loss due to the next object alignment)

关闭指针压缩

加上-XX:-UseCompressedOops

not-use-compress-empty-object

对象大小 = Mark Word + 类型指针 + 数组长度 + 实例数据(字段4字节补齐) + 对齐填充

16B = (4 + 4) + (4 + 4) + 0 + 0 + 0

普通字段对象

包含有属性的字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class OrdinaryObjectMemoryLayout {

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

class Person {
    private String str1;
    private float f1;
    private int i1;
    private byte b1;
    private short s1;
    private double d1;
    private long l1;
    private char c1;
    private boolean bool1;
}

开启指针压缩

默认或者加上-XX:+UseCompressedOops

ordinary-object-use-compressed-oops

对象大小 = Mark Word + 类型指针 + 数组长度 + 实例数据(字段4字节补齐) + 对齐填充

48B = (4 + 4) + 4 + 0 + (4 + 8 + 8 + 4 + 2 + 2 + 1 + 1 + 2 + 4) + 0

关闭指针压缩

加上-XX:-UseCompressedOops

ordinary-object-not-use-compressed-oops

对象大小 = Mark Word + 类型指针 + 数组长度 + 实例数据(字段4字节补齐) + 对齐填充

56B = (4 + 4) + (4 + 4) + 0 + (8 + 8 + 4 + 4 + 2 + 2 + 1 + 1 + 2 + 8) + 0

字段的分配顺序

括号&大小一样取决于声明顺序

(double & long) > (int & float) > (char & short) > byte > boolean > object reference

数组对象

1
2
3
4
5
6
7
public class ArrayObjectMemoryLayout {

    public static void main(String[] args) {
        int[] arr = new int[10];
        System.out.println(ClassLayout.parseInstance(arr).toPrintable());
    }
}

开启指针压缩

默认或者加上-XX:+UseCompressedOops

array-object-use-compressed-oops

对象大小 = Mark Word + 类型指针 + 数组长度 + 实例数据(字段4字节补齐) + 对齐填充

56B = (4 + 4) + (4) + 4 + 40 + 0

关闭指针压缩

加上-XX:-UseCompressedOops

array-object-not-use-compressed-oops

对象大小 = Mark Word + 类型指针 + 数组长度 + 实例数据(字段4字节补齐) + 对齐填充

64B = (4 + 4) + (4 + 4) + (4 + 4) + 40 + 0

指针压缩的本质

C/C++中的共用体

共用体占用的内存等于最长的成员占用的内存

1
2
3
union 共用体名{
    成员列表
};

HotSpot中的Class Pointer

https://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/69087d08d473/src/share/vm/oops/oop.hpp#l63

1
2
3
4
5
union _metadata {
    Klass*      _klass; //8B
    narrowKlass _compressed_klass;  //4B
} _metadata;

jhsdb验证

普通字段对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class JhsdbOrdinary {

    public static void main(String[] args) {
        HsdbTest hsdbTest = new HsdbTest();
        System.out.println(ClassLayout.parseInstance(hsdbTest).toPrintable());

        LockSupport.park();
    }
}

class HsdbTest{
    private int x = 15;
    private String name = "hsdb";
}

开启指针压缩

hsdb-ordinary-object-use-compressed-oops

关闭指针压缩

hsdb-ordinary-object-not-use-compressed-oops

数组对象

1
2
3
4
5
6
7
8
9
public class JhsdbArray {

    public static void main(String[] args) {
        int[] array = new int[10];
        System.out.println(ClassLayout.parseInstance(array).toPrintable());

        LockSupport.park();
    }
}
开启指针压缩

hsdb-array-use-compressed-oops

关闭指针压缩

hsdb-array-not-use-compressed-oops

结论

核心本质, union共同体在HotSpot中始终占8B

  • 开启指针压缩

    • 普通对象
      • 会把实例数据中某个4B(int、float、reference, 按照声明顺序的第一个)放到union联合体里Class Pointer的前面
    • 数组对象
      • 会把数组长度放到union联合体里Class Pointer的前面
  • 关闭指针压缩

    • 普通对象

      • Class Pointer占8B, 实例数据按照规定(前文阐述的字段分配顺序)排列
    • 数组对象

      • Class Pointer占8B, 数组长度按照8B对齐放到Class Pointer后的数组长度位置

参考资料

子牙老师

聊聊Java对象在内存中的大小

C语言共用体