文章目录
JVM内存布局
一个对象主要包含下面3个部分:
- 对象头(Header)
- 实例数据(Instance Data)
- 对齐填充(Padding)
对象头
32位jvm对象头:8字节
64位jvm对象头:
- 16字节(不开启指针压缩情况,-XX:-UseCompressedOops)
- 12字节(开启指针压缩,-XX:+UseCompressedOops)
数组对象对象头:
- 24字节(不开启指针压缩情况,-XX:-UseCompressedOops)
- 16字节(开启指针压缩,-XX:+UseCompressedOops)
对象头也包含2部分数据:
- MarkWord:用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等
- KlassPointer:指向它的类元数据的指针,JVM通过该指针来确定这个对象是哪个类的实例
实例数据
数据类型 | 占用字节 |
---|---|
boolean | 1 |
byte | 1 |
short | 2 |
char | 2 |
int | 4 |
float | 4 |
long | 8 |
double | 8 |
引用类型32位JVM | 4 |
引用类型64位JVM | 8 |
引用类型64位JVM开启指针压缩 | 4 |
对齐填充
JVM按8字节对齐,不足8字节要填充(Padding)到8字节的整数倍。
填充字节数很容易计算:padding = 8 - ((对象头 + 实例数据) % 8)
计算实例
下面的计算实例都是在64位JVM开启指针压缩下的情况。
怎样计算对象占用内存大小呢?后面介绍2中方式:
- 使用Java8的jdk.nashorn.internal.ir.debug.ObjectSizeCalculator类的getObjectSize方法
- 使用ava.lang.instrument.Instrumentation类的getObjectSize方法(不能直接调用,得使用-javaagent方式,Instrumentation有JVM注入)
数组占用内存大小
数组计算:
MarkWord + KlassPointer + 数组长度 + 实例数据(数组长度*数组元数据大小) + 补齐填充
- 开启指针压缩:MarkWord(8字节) + KlassPointer(4字节) + 数组长度(4字节) + 0*4 + 补齐 0 = 16
- 关闭指针压缩:MarkWord(8字节) + KlassPointer(8字节) + 数组长度(4字节) + 0*8 + 补齐 0 = 24
默认是开启指针压缩,可以通过下面参数来关闭:
# 关闭指针压缩
-XX:-UseCompressedClassPointers -XX:-UseCompressedOops
# 查看最终生效的参数
-XX:+PrintFlagsFinal
# 查看默认的初始参数
-XX:+PrintFlagsInitial
相信,有亲子动手试过的朋友会发现,Java8的ObjectSizeCalculator类的getObjectSize方法计算出来的值都是和开启指针压缩一致。
刚开始,我以为关闭指针压缩没有生效,找了半天原因,最后使用Instrumentation的getObjectSize能对上,参数生效了,应该是ObjectSizeCalculator的问题。
Instrumentation方式稍微麻烦一些,放在后面介绍。
@Test
public void memoryArray() {
int[] base = new int[0];
System.out.println("int[0]占用内存大小:" + ObjectSizeCalculator.getObjectSize(base));
int[] a = new int[1];
System.out.println("int[1]占用内存大小:" + ObjectSizeCalculator.getObjectSize(a));
int[] b = new int[2];
System.out.println("int[2]占用内存大小:" + ObjectSizeCalculator.getObjectSize(b));
int[] c = new int[3];
System.out.println("int[3]占用内存大小:" + ObjectSizeCalculator.getObjectSize(c));
Integer[] d = new Integer[2];
System.out.println("Integer[2]占用内存大小:" + ObjectSizeCalculator.getObjectSize(d));
Integer[] e = new Integer[3];
System.out.println("Integer[3]占用内存大小:" + ObjectSizeCalculator.getObjectSize(e));
}
结果如下:
int[0]占用内存大小:16
int[1]占用内存大小:24
int[2]占用内存大小:24
int[3]占用内存大小:32
Integer[2]占用内存大小:24
Integer[3]占用内存大小:32
计算逻辑如下:
int[1] = 数组对象头16字节 + 1个int类型4字节 + 4个字节的padding = 24字节
int[2] = 数组对象头16字节 + 2个int类型4字节(8字节) = 24字节
int[3] = 数组对象头16字节 + 3个int类型4字节(12字节) + 4个字节的padding = 32字节
我们再看一个容易出错的char数组:
public static void memoryCharArray() {
char[] base = new char[0];
System.out.println("int[0]占用内存大小:" + ObjectSizeCalculator.getObjectSize(base));
char[] a = new char[1];
System.out.println("int[1]占用内存大小:" + ObjectSizeCalculator.getObjectSize