JOL:分析Java对象的内存布局 java一个对象占用多少字节

Java 对象的内存布局

Java的实例对象、数组对象在内存中的组成包括如下三部分:对象头Hearder、实例数据、内存填充。示意图如下所示

JOL:分析Java对象的内存布局 java一个对象占用多少字节

  • 对象头:其主要包括两部分数据:Mark Word、Class对象指针。特别地对于数组对象而言,其还包括了数组长度数据。在64位的HotSpot虚拟机下,Mark Word占8个字节,其记录了Hash Code、GC信息、锁信息等相关信息;而Class对象指针则指向该实例的Class对象,在开启指针压缩的情况下占用4个字节,否则占8个字节;如果其是一个数组对象,则还需要4个字节用于记录数组长度信息。这里列出64位HotSpot虚拟机Mark Word的具体含义,以供参考。需要注意的是在下图的Mark Word中,左侧为高字节,右侧为低字节

 JOL:分析Java对象的内存布局 java一个对象占用多少字节

  • 实例数据:用于存放该对象的实例数据
  • 内存填充64位的HotSpot要求Java对象地址按8字节对齐,即每个对象所占内存的字节数必须是8字节的整数倍。因此Java对象需要通过内存填充来满足对齐要求(默认情况下,Java 虚拟机堆中对象的起始地址需要对齐至 8 的倍数,所有需要空白填充来使"内存对齐")

 

Note:

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

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

 

 

Java数据类型有哪些

  • 基础数据类型(primitive type)
  • 引用类型 (reference type)

基础数据类型内存占用如下

JOL:分析Java对象的内存布局 java一个对象占用多少字节

 

 

引用类型内存占用如下

引用类型跟基础数据类型不一样,除了对象本身之外,还存在一个指向它的引用(指针),指针占用的内存在64位虚拟机上8个字节,如果开启指针压缩是4个字节,默认是开启了的。

字段重排序

为了更高效的使用内存,实例数据字段将会重排序。排序的优先级为: long = double > int = float > char = short > byte > boolean > object reference
如下所示的类

1 class FieldTest{
2         byte a;
3         int c;
4         boolean d;
5         long e;
6         Object f;
7     }

将会重排序为:

1      OFFSET    SIZE         TYPE DESCRIPTION            
2          16     8               long FieldTest.e            
3          24     4                int FieldTest.c            
4          28     1               byte FieldTest.a            
5          29     1            boolean FieldTest.d            
6          30     2              (alignment/padding gap)
7          32     8   java.lang.Object FieldTest.f

 

PS:在后文使用JOL工具测试时,好像也不一定会按照这个顺序来,感觉是会遵循一个最优的排序,也就是使得排序后占用内存最小,不是很确定,备注一下。

 

实践

利用JOL工具分析很简单,首先在POM添加添加其Maven依赖

1 <!-- JOL依赖 -->
2 <dependency>
3    <groupId>org.openjdk.jol</groupId>
4    <artifactId>jol-core</artifactId>
5    <version>0.9</version>
6 </dependency>

 

例1:类对象占用大小

 1 package com.study;
 2 
 3 import org.openjdk.jol.info.ClassLayout;
 4 
 5 class Test{
 6     private long orderId;
 7     private byte state;
 8     private long createMillis;
 9     private char c;
10     private int i;
11     private double d;
12     private long userId;
13 }
14 
15 
16 public class Test06 {
17     public static void main(String[] args) {
18         System.out.print(ClassLayout.parseClass(Test.class).toPrintable());
19     }
20 }

结果如下:

JOL:分析Java对象的内存布局 java一个对象占用多少字节

 

例2:实例对象占用内存大小

 1 package com.study;
 2 
 3 import lombok.Builder;
 4 import lombok.Data;
 5 import org.openjdk.jol.info.ClassLayout;
 6 
 7 @Data
 8 @Builder
 9 class Car {
10     private int id;
11     private String type;
12     private double price;
13     private char level;
14 }
15 
16 public class JOLDemo {
17     public static void main(String[] args) {
18         Car car = Car.builder()
19                 .id(1)
20                 .type("SUV")
21                 .level('A')
22                 .price(22.22)
23                 .build();
24 
25         System.out.println(ClassLayout.parseInstance(car).toPrintable());
26 
27         int[] array = new int[3];
28         array[0] = 11;
29         array[1] = 22;
30         array[2] = 33;
31         System.out.println(ClassLayout.parseInstance(array).toPrintable());
32     }
33 }

结果是:

JOL:分析Java对象的内存布局 java一个对象占用多少字节

 

如果设置了JVM选项-XX:-UseCompressedOops以关闭指针压缩。下面即是Java对象的内存布局信息输出及相关分析结果,这里笔者的CPU主机字节序为小端

 JOL:分析Java对象的内存布局 java一个对象占用多少字节

 

例3:父类的私有成员变量是否会被子类继承?

 1 package com.study;
 2 
 3 import org.openjdk.jol.info.ClassLayout;
 4 
 5 class Fruit {
 6     private int size;
 7     public String name;
 8 }
 9 
10 class Apple extends Fruit {
11     private int size;
12     private String name;
13     private Apple brother;
14     private long create_time;
15 }
16 
17 
18 public class JOLDemo {
19     public static void main(String[] args) {
20         System.out.println(ClassLayout.parseClass(Fruit.class).toPrintable());
21         System.out.println(ClassLayout.parseClass(Apple.class).toPrintable());
22     }
23 }

结果如图:
JOL:分析Java对象的内存布局 java一个对象占用多少字节

 

 

总结

字段重排列,顾名思义,就是 Java 虚拟机重新排列字段的在内存中的顺序,以达到内存利用率最大,即减少内存填充。Java 虚拟机中有三种排列方法(对应 Java 虚拟机选项 -XX:FieldsAllocationStyle,默认值为 1),但都会遵循如下两个规则。

  • 其一、如果一个字段占据 C 个字节,那么该字段的偏移量需要对齐至 NC。这里偏移量指的是字段地址与对象的起始地址差值。
  • 其二,子类所继承字段的偏移量,需要与父类对应字段的偏移量保持一致

 

上一篇:ArrayList 扩容机制


下一篇:阻塞队列&线程池的学习记录