Java对象大小计算与MAT内存泄露分析

JVM内存布局

一个对象主要包含下面3个部分:

  1. 对象头(Header)
  2. 实例数据(Instance Data)
  3. 对齐填充(Padding)

对象头

32位jvm对象头:8字节

64位jvm对象头:

  1. 16字节(不开启指针压缩情况,-XX:-UseCompressedOops)
  2. 12字节(开启指针压缩,-XX:+UseCompressedOops)

数组对象对象头:

  1. 24字节(不开启指针压缩情况,-XX:-UseCompressedOops)
  2. 16字节(开启指针压缩,-XX:+UseCompressedOops)

对象头也包含2部分数据:

  1. MarkWord:用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等
  2. 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中方式:

  1. 使用Java8的jdk.nashorn.internal.ir.debug.ObjectSizeCalculator类的getObjectSize方法
  2. 使用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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值