上一篇[Java 进阶之字节码剖析] 中我曾经提到这么一段话
int[128][2] ,int[256] 这两个数组看起来一样,但实际上前者比后者多了 246% 的额外开销
针对这句话我收到了几位读者的私信,表示不明白为啥不过一个简单的二维数组会有这么大的开销,本来这个问题在我正在写的类加载机制中有详述,不过文章还没写完(估计本周发),所以我专门抽出这个问题探讨一下,五分钟就能看懂
HotSpot JVM 底层使用名为 oops (Ordinary Object Pointers) 的数据结构来表示对象的对象头
https://mp.weixin.qq.com/s/QWOjT0jS3uZuMQIpJE4acQ
JVM 每创建一个对象,相当于创建了一个 oopDesc 的对象,即 instanceOopDesc
来表示这个对象,保存在堆中,如下图所示
可以看到 Java 对应主要由以下三部分组成
其中对象头又包含三个部分
除此之外对象还有两个部分值得我们注意
知道了对象模型的表示,再来看数组的大小,首先必须明确两点
接下来我们来看看一维数组 int[256] 在内存中有多大,一维数组其实可以认为是普通的对象,首先对象头可以知道是 8(markword) + 4(kclass) + 4(数组长度)= 16 字节,对象实际数据大小为 256 * 4(int 大小为 4 个字节) = 1024 字节,所以此时总的字节数为 16 + 1024 = 1040 字节,是 8 的位数(1040/8 = 130),所以 padding 为 0, 也就是说 int[256] 一维数组的字节大小为 1040 字节
再来看一下二维数组 int[128][2] 的大小,我们知道在 C 语言中二维数组(事实上是任何多维数组)本质上是一维数组通过指针操作来实现的,但在 Java 中多维数组是由一系列的嵌套数组组成,也就是说对于二维数组而言,每一行(int[0][…],int[1][…],…,int[127][…])都对应一个数组对象,都需要额外的开销,一图胜千言,如下所示
先来看左边的对象大小:
数组的每一行 int[0],int[1],..int[127] 其实都是指向数组的指针,为 4 个字节,所以左边对象占用空间大小为 16 + 4 * 128 = 528,是 8 的倍数(528/8 = 66),所以 padding 为 0,所以总大小为 528
再来看左边的 int[0] 等指向的数组对象大小:
由于左边每个行数组的指向都指向了两个元素的数组(int[x][0],int[x][1]),它们的对象大小为 16 + 4 + 4 = 24,是 8 的倍数,所以 padding 为 0 ,而总共有 128 个这样的对象,所以右边总的对象大小为 128 * 24 = 3072
由此可知 int[128][2] 对象大小为 528 + 3072 = 3600 字节,比一维数组 int[256](1024 字节)多了 246% !
上述计算的是否正确呢,我们可以用 JDK 自带的 ObjectSizeCalculator 来计算一下,如下:
与我们的计算结果完全一致!
其实不光是二维数组,包括字节串,普通的对象开销也一般会比对象实际数据大几倍,到此我相信你不难明白上一篇中开头这样一段话的含义了:kafka 中为啥要使用 pageCache 了, 因为如果不用页缓存,而是用 JVM 进程中的缓存,对象的内存开销会非常大(通常是真实数据大小的几倍甚至更多)
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8