JVM拾遗(5): 对象大小计算

JVM拾遗(4): Java对象的创建及内存布局讲了JVM如何实例化对象以及对象在内存中的表示.
本次讲解如何计算一个对象的大小.

为啥要讲这个? 因为笔者在做一块业务的时候,老板有次让从数据库取100000条天气数据,在内存里按业务规则排序.
这明显不合理会让内存爆炸的需求,怎么怼回去呢?

这时候就要搬出我们的理论支持来计算一番了.

1. 数据类型及大小

这个基本功不扎实的同学可能不清楚, 不过相信大部分老司机都知道.

JVM的数据类型分为基本数据类型引用数据类型.

基本数据类型有:

  • long/double: 8字节, 长整型和双精度浮点型
  • int/float: 4字节, 整数和浮点数
  • char,short: 2字节,字符型和短整型
  • byte: 1字节, 整数

在JDK8, 64位HotSpot上, 引用数据类型都是直接指针, 如果开了压缩指针,就是4字节,没开就是8字节

用原生数据类型就是为了提高性能的.

后来为了满足一切皆对象的概念和泛型系统,出了一堆包装类, 造成装箱和拆箱的一堆性能问题不说, 还浪费内存.

比如一个int原生类型才4字节,而Integer包装类对象头就至少12字节了..

2. 对象的大小包含哪些

前面讲过一个对象包含3部分数据:

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

对象头前面说过, 在64位的虚拟机上开了压缩指针就是12字节,没开就是16字节.
实例数据的大小依据数据类型的大小来计算, 注意要子类的对象大小要把父类的实例数据大小也计算进去.

对齐填充是按照对象里最宽的数据类型的大小来对齐的, 比如最大的是long8字节, 那么就是按照8的倍数来对齐.

3. 案例

把前面提到的天气数据类简化,算算100000个天气对象的大小,如下:

1
2
3
4
5
6
7
public class WeatherDO {
private double pm25;
private int uvLevel;
private String tips;

...
}

按照理论,开启压缩指针后,对象头占12字节, 实例数据最长的pm25是8个字节, int是4字节, String是引用类型,占4字节, 按照8字节对齐.
总共是12+8+4+4=28字节,按照8字节对齐是32字节,要4个字节的对齐填充.

Java Object Layout, JOL

JOL是openjdk提供的用来验证JVM的内存布局方案的工具.
工具内部使用了Unsafe, JVMTI(工具接口, 后面会聊), ServiceabilityAgent(SA)来计算,比基于堆转储(heap dump)出来的会更精确.
我们可以去maven仓库挑个版本将jar包下下来使用.

1
$ curl -O -L http://central.maven.org/maven2/org/openjdk/jol/jol-cli/0.9/jol-cli-0.9.jar

然后这样使用:

1
$ java -cp .:./jol-cli-0.9-full.jar org.openjdk.jol.Main internals WeatherDO

得到的结果(JVM默认开启压缩指针):

1
2
3
4
5
6
7
8
9
10
11
WeatherDO object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) a3 11 01 f8 (10100011 00010001 00000001 11111000) (-134147677)
12 4 int WeatherDO.uvLevel 0
16 8 double WeatherDO.pm25 0.0
24 4 java.lang.String WeatherDO.tips null
28 4 (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

可以看到,Instance size=32字节, 和我们的计算一致, 十万条对象数据就是3M.
针对笔者当时的业务场景,这是一个频繁的需求,一小块业务就几M的对象, 高并发下就杯具了:)

4. 总结

本节回顾了java对象的内存布局,并指出Hotspot虚拟机实现下对象的大小计算方法.