本文转载自微信公众号《码海》,作者坤哥。转载本文请联系码海公众号。大家好,我是鲲哥。在上一篇Java高级字节码分析中,曾经提到过这样一段话。int[128][2]和int[256]这两个数组看起来一样,但实际上前者比后者多了246%的开销。针对这句话,收到了好几个读者的私信,说不明白为什么一个简单的二维数组会有这么大的开销。本来这个问题写在类加载机制里面有详细的类加载机制,但是文章还没写完(估计这周发),所以特意拿出这个问题来讨论,以及我可以在五分钟内理解Java对象模型。HotSpotJVM底层称为oops(OrdinaryObjectPointers)数据结构,表示每次对象classoopDesc{friendclassVMStructs;private:volatilemarkOop_mark;union_metadata{Klass*_klass;narrowKlass_compressed_klass;}_metadata;...}的对象头JVM创建了一个对象,就相当于创建了一个oopDesc对象,即instanceOopDesc来代表这个对象,存放在堆中。如下图所示,可以看到Java对应关系主要由以下三部分组成:对象头(Header)、对象实例数据(instancedata)、对齐填充(Padding)。markWord部分:即_mark:markOop,用于存储对象运行时的数据,如HashCode、锁状态标志、GC分代年龄等。这部分在64位操作系统下占用8字节,4bytesunderthe32-bitoperatingsystemPointer:指向方法区中的类元数据(类信息)的指针,这部分涉及到指针压缩的概念,在启用指针压缩时占4字节,不启用时占8字节未启用。默认为启用的数组长度:这部分只对数组对象有效,对非数组对象无效。这部分占4个字节。除此之外,对象还有两部分值得我们关注:对象实例数据(instanceData):用于存储对象中的各种类型的字段信息(包括继承自父类的)alignmentpadding:Java对象大小默认为8字节对齐,如果“对象头”+“对象实际数据”不足8位,对齐填充会补满相应的字节,使对象大小达到8的倍数Java数组大小知道表示对象模型,再看数组的大小,首先要明确两点。在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]),他们的objectsize是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,因为如果不使用pagecache,而是使用JVM进程中的缓存,那么内存对象的开销会很大(通常是真实数据大小的几倍甚至更多)
