在计算机发展初期,硬件开发速度慢,容量小,所以软件开发人员在编写代码时“一心想着字节和比特”,然后利用写好的应用性能在我们今天看到的小巧低调的硬件上运行良好,而且效果惊人。所以电脑发展到今天,硬件配置好像越来越高了,但是还是不能支持你随便写,要不然应用就挂了。此外,有些行业,比如游戏,还是要“一分钱一分货”,应用才能稳定运行。在Java应用中,要想计算准确,就需要知道对象的大小。我们来看看这篇文章。在Java世界中,对象的大小是多少?计算物体大小的方法有哪些。要看一个对象的大小,首先要看Java运行的平台是32位的还是64位的,然后还要看这个对象有多少个属性(字段)。最后,还有一些JVM自身需要记录在对象中的信息,比如GC状态、同步状态、数组长度等信息。这些项目的总和基本上是一个物体的大小。但是为了效率,VM会使用固定长度,比如8位的整数倍,来统一存储。在这种情况下,如果原始对象的大小不够,则会扩展对齐方式进行存储。好了,我们来看看JVM中一个Java对象的大小。由于现在基本的操作系统基本都是64位的,后面我们就用64位的JVM来说明。我们先来看两个例子:publicclassApp{}publicclassApp{privatebytea;privateintb;例1中,App对象占用多少字节?答案是16。例2多少钱?答案是24。这里是怎么计算的?计算方法与开头的文字描述类似。在Java中,一个对象的大小就是由这些内容组成的。Objectsize=Header+PrimitiveFields+ReferenceFields+Alignment&Padding`Header部分,是JVM用来记录具体信息的。一种版本也称为对象标头。在OpenJDK的summarypage中,是这样描述的:每个GC管理的堆对象开头的commonstructure。(每个oop指向一个对象头。)包括有关堆对象的布局、类型、GC状态、同步状态和身份哈希代码的基本信息。由两个词组成。在数组中,它紧跟一个长度字段。请注意,Java对象和VM内部对象都具有通用的对象标头格式。我们看到objectheader由这几部分组成:markwordklasspointer(Optional)如果是数组,会记录数组的长度markword每个objectheader的第一个word。通常是一组位域,包括同步状态和身份哈希码。也可能是指向同步相关信息的指针(具有特征低位编码)。GC期间,可能包含GC状态bits.klass指针每个对象头的第二个字。指向描述原始对象的布局和行为的另一个对象(元对象)。对于Java对象,“klass”包含一个C++风格的“vtable”,在对象头中,基本上GC状态,同步状态,身份哈希码,数组长度,类元信息的指针头长度两个字组成。64位VM中的markword,长度为8字节。klass指针的长度在64位VM下由参数-XX:+UseCompressedOops控制,可能是4个字节,也可能是8个字节。例1,我们看关闭情况-XX:-UseCompressedOopsOFFSETSIZETYPEDESCRIPTIONVALUE04(objectheader)01000000(0000000100000000000000000000000)(1)44(objectheader)00000000(00000000000000000000000000000000)(0)84(objectheader)38c46a97(00111000110001000110101010010111)(-1754610632)124(objectheader)01000000(00000001000000000000000000000000)(1)Instancesize:16bytesSpacelosses:0bytesinternal+0bytesexternal=0bytestotal16bytesareall对象头。如果启用,对象头占12个字节,其他4个字节将被填充。OFFSETSIZETYPEDESCRIPTIONVALUE04(objectheader)01000000(00000001000000000000000000000000)(1)44(objectheader)00000000(00000000000000000000000000000000)(0)84(objectheader)05c000f8(00000101110000000000000011111000)(-134168571)124(lossduetothenextobjectalignment)Instancesize:16bytesSpacelosses:0bytesinternal+4bytesexternal=4bytestotal例子2,输出是这样:OFFSETSIZETYPEDESCRIPTIONVALUE04(objectheader)01000000(00000001000000000000000000000000)(1)44(objectheader)00000000(00000000000000000000000000000000)(0)84(objectheader)05c100f8(00000101110000010000000011111000)(-134168315)124intApp.b0161byteApp.a0177(lossduetothenextobjectalignment)Instancesize:24bytesSpacelosses:0bytesinternal+7bytesexternal=7bytestotal增加了字段的占用。这里的占用大小就是我们常说的基本数据类型的大小。如果有对象引用类型,只需添加这些类型的大小。工具以下工具和方法可用于计算对象的大小。1、JOLJOL是OpenJDK提供的一个工具,在项目中添加依赖后可以直接使用。null
.*/ThenhowtogettheInstrumenttocallthismethod,usuallybyattachingtheAgenttotheVM,oneistheAgenttoimplementthepremainmethod,oneItistheagentMainmethod,thedifferenceliesinthetimingofattach.比如你定义一个Agentjarimportjarimportjava.lang.instrument.Instrumentation;publicclassObjectSizeFetcher{privatestaticInstrumentationinstrumentation;publicstaticvoidpremain(Stringargs,Instrumentationinst){instrumentation=inst;}publicstaticlonggetObjectSize(Objecto){returninstrumentation.getObjectSize我们按照代码(o)}}直接静态调用getObjectSize方法即可。3.SA我在之前的文章(Java虚拟机的MicroscopeServiceabilityAgent)中介绍了SA(ServiceablityAgent)。通过SA,可以做很多事情来分析JVM。当然,观察对象的构成和大小不是问题。通过SA观察上面的例子2,效果如下:我们可以看到整个对象先是_mark(标记词),然后是压缩的klass(klass指针),然后是实例中包含的属性。如果红框显示,则klass指针中有一个_layout_helper,显示对象的大小。另外,在SA中的Console中,可以像命令行一样进行交互,还可以显示对象的大小。PS:对于对象的大小,JVM在初始化分配时,会对字段进行“重新排序”,对字段的分配进行排序,从而节省空间。比如先赋值byte或者boolean,再赋值int,那么byte之后可能需要增加3个字节进行填充,重新排序可以减少空间占用。比如有这些属性:publicclassApp{privatebytea;privateintb;privatebooleanc;privatefloatd;privatechare='a';}OFFSETSIZETYPEDESCRIPTIONVALUE04(objectheader)01000000(00000001000000000000000000000000)(1)44(objectheader)00000000(00000000000000000000000000000000)(0)84(objectheader)61c100f8(01100001110000010000000011111000)(-134168223)124intApp.b0164floatApp.d0.0202charApp.ea221byteApp.a0231booleanApp.cfalseInstancesize:24bytesSpacelosses:0bytesinternal+0bytesexternal=0bytestotal在输出对象内容,我们发现,并不是按属性的声明顺序来分配的,这样只占用24Bytes,ifyoufollowtheorderofdeclaration,youshouldallocatebytesfirst,andthenallocateints.Inthisway,foralignment,anadditional3bytes(alignment/paddinggap)willbeaddedandmayeventuallyoccupy32bytes.Inordertoreducespacewaste,ingeneral,theorderofpriorityforfieldallocationis:double>long>int>float>char>short>byte>boolean>objectreference.Thereisabasicprinciplehere:allocatethetypesthatoccupyalargespaceasmuchaspossiblefirst.
