JVM字节对齐和字段重排 Bertha 。 2022-12-04 08:44 103阅读 0赞 **目录** 字节对齐 struct示例 内存布局 优化结构体内存空间 Java字节对齐 -------------------- 上一篇日志最后说到,非静态类型变量经过起始偏移量和5中类型偏移量计算后,在统计总的内存空间大小前,需要进行内存对齐补白操作,这样做的原因是性能带来了提升,上一篇举过例子,对于进行了补白对齐的数据,CPU进行访问可能只需要一个周期即可,反过来如果内存起始地址并不是数据字节的倍数,那么可能读取某一数据时需要分两步到三步,将不同内存地址的数据读取出来后,去除头和尾多余的字节拼凑出目标地址的结果出来。总的来说,如果内存起始地址是x字节长度的倍数,那么说明该x字节是内存对齐的,32位操作系统下例如int变量类型长度为4个字节,那么规则也是按4的倍数来对齐。 ## 字节对齐 ## 如果变量数据的内存起始地址是字节对齐的,那么对该数据的读取就是高效安全的,因为不需要分多个周期进行读取,拼凑。对于Java和C、C++、C\#这类高级语言,因为编译器的存在,编译器或虚拟机会自动帮我们进行字节对齐补白,来看一个例子。 ### struct示例 ### ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2p1c3RpbnplbmdUTQ_size_16_color_FFFFFF_t_70][] 上面的程序中先是声明了两个结构体A和B,它们里面都有三个变量,int i、char c和short s,在32位系统下int型的i变量大小为4个字节,char型变量c大小1字节,short型变量大小为2字节,虽然结构体A和B中三个变量都相同,但是声明顺序不同,在输出两个结构体的大小后我们可以发现,它们的大小也并不相同: ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2p1c3RpbnplbmdUTQ_size_16_color_FFFFFF_t_70 1][] 可以看到,先声明了int i再声明char c的结构体A大小为8个字节,而先声明char c再声明int i的结构体B大小去到了12个字节,表面上看两个结构体的大小应该是三个变量的大小相加4+1+2等于7,实际却没有一个符合,原因就是编译器对变量进行了内存对齐补白,且这种对齐操作受变量声明顺序影响。 ### 内存布局 ### ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2p1c3RpbnplbmdUTQ_size_16_color_FFFFFF_t_70 2][] 首先来看struct A,声明变量的顺序是int i到char c再到short s,为结构体分配内存空间时,首先分配4字节给int i,假设此时变量i的起始地址为4的整数倍4x,接着分配1字节大小给char c,因为char类型大小为1字节,地址可以从任何位置开始,不用进行内存对齐,则变量c的地址位4(x+1)。最后到为short s变量分配内存空间,short类型变量大小为2字节,如果直接跟在char变量c后面,那么它的起始地址就是4(x+1)+1,这样起始地址结果并不能被2整除,所以编译器会进行内存对齐,让它符合2字节对齐的要求,具体的做法就是在变量c后面往后移动一个字节,再存放变量s,c和s之间多出来的一个字节就是补白字节,因此最终struct A的大小就是4+1+1+2等于8。 再来看结构体B: ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2p1c3RpbnplbmdUTQ_size_16_color_FFFFFF_t_70 3][] 结构体B中首先为char型变量c分配内存大小1字节,此时它的起始地址设为4x,没错起始地址就是4的倍数,因为编译器不光会对数据类型进行内存对齐操作,对栈和堆的内存地址也会进行对齐操作。接着下一个变量int i,因为需要符合4字节对齐,下一个起始地址应该是4x+4的位置,所以c变量往后移动3字节进行补白,得到的位置4x+4能够被4整除,同理最后一个变量s也是相对于变量i往后移动4个字节,起始地址是4x+4+4,最后structB总的内存大小为1+3+4+4等于12字节。 ### 优化结构体内存空间 ### 从上面两个例子可以看到,包含相同变量的两个结构体,由于声明变量的顺序不同,最后为结构体申请的内存大小也不同。虽然编译器会帮我们进行内存对齐,但它也仅仅是按顺序为我们每一个变量做补白操作,如果声明变量的顺序不好,最后需要申请很大的内存空间来存储。再举个例子,假设我们声明的结构体如下: struct A { char c_1; int i_1; char c_2; int i_2; }; 由于每次为char变量分配1字节空间后,下一个变量int类型需要进行对齐补白,所以编译器会自动填充3个字节,最后整个结构体需要的内存大小为1+3+4+1+3+4等于16字节。想要优化内存空间的大小,我们可以手动进行字段重拍,因为我们知道编译器填充的逻辑,只要改变一下变量的声明顺序,就可以避免一些不必要的补白操作: struct A { char c_1; char c_2; int i_1; int i_2; }; 就像这样,如果我们在声明变量时,按照类型的大小从小到大顺序,那么可以减少一些补白空间,上面这个字段重拍后的结构体最后需要的内存空间为1+1+3+4+4+3等于16字节。 ## Java字节对齐 ## JVM对其内部5中静态类型变量,oop、double、word、short和byte都有内存对齐的规则,oop引用类型按4字节对齐;double按8字节对齐;word按4字节;short按2字节,最后byte类型按1字节对齐。字节对齐自然就是确保内存地址能够被其类型字节宽度所整除,JVM为了优化内存空间利用率,采用了上述的字段重拍方式,将相同类型的字段组合在一起,减少一些补白操作,提升了整个空间利用率。 [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2p1c3RpbnplbmdUTQ_size_16_color_FFFFFF_t_70]: /images/20221123/64f149e354a044daa2d168bc9cf0e8a3.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2p1c3RpbnplbmdUTQ_size_16_color_FFFFFF_t_70 1]: /images/20221123/7bf0317dfa244f8a97d9b8438a95aa23.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2p1c3RpbnplbmdUTQ_size_16_color_FFFFFF_t_70 2]: /images/20221123/6f25da8cf4c546b693578781d221c456.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2p1c3RpbnplbmdUTQ_size_16_color_FFFFFF_t_70 3]: /images/20221123/ed754f663b184b8eadbe385ce33144a2.png
还没有评论,来说两句吧...