我会建议不要对一个实际的JVM实现依赖于自己“理论计算对象布局”做假设。毕竟代码日新月异,如果不是每天都直接跟JVM内部实现打交道的话,自己的“理论”什么时候过时了或者哪里有个啥疏漏也难以察觉。
针对HotSpot VM,这种问题现在有超级方便的工具来解答了:发布在OpenJDK上的JOL(Java Object Layout),由Aleksey Shipilëv大大开发。
官网链接:OpenJDK: jol
这是我在Mac OS X上跑Oracle JDK8u101,关掉压缩指针得到的输出:
$ java -XX:-UseCompressedOops -Djol.tryWithSudo=true -jar ~/Downloads/jol-cli-0.6-full.jar internals java.util.ArrayList
objc[49091]: Class JavaLaunchHelper is implemented in both /Users/krismo/sdk/jdk1.8.0_101/Contents/Home/bin/java and /Users/krismo/sdk/jdk1.8.0_101/Contents/Home/jre/lib/libinstrument.dylib. One of the two will be used. Which one is undefined.
# Running 64-bit HotSpot VM.
# Objects are 8 bytes aligned.
# Field sizes by type: 8, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 8, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
java.util.ArrayList 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) f0 ad e9 23 (11110000 10101101 11101001 00100011) (602516976)
12 4 (object header) 02 00 00 00 (00000010 00000000 00000000 00000000) (2)
16 4 int AbstractList.modCount 0
20 4 (alignment/padding gap) N/A
24 4 int ArrayList.size 0
28 4 (alignment/padding gap) N/A
32 8 Object[] ArrayList.elementData []
Instance size: 40 bytes
Space losses: 8 bytes internal + 0 bytes external = 8 bytes total其中,关掉压缩指针时的对象头有_mark和_klass两个字段,每个都是8字节;
然后在类继承边界上(ArrayList继承AbstractList)也有对齐带来的padding。这个例子中就是位于offset +20的那4字节。题主没想明白的大概就是这里。
在4字节宽的字段与8字节宽的字段之间也可能会有对齐带来的padding(因为8字节宽的字段需要8字节对齐,而前面的4字节宽字段只需要4字节对齐),这就是位于offset +28的那4字节。
就这样。
本来在继承边界上的padding也可以用来分配给派生类的合适宽度的字段用,但HotSpot VM并没有这样做。算是个缺陷吧。
HotSpot VM在计算派生类的字段的布局时,直接先问基类的实例大小有多大(但却没有问基类实例的末尾有没有padding),然后从基类的实例大小之后开始计算派生类字段的偏移量。所以基类末尾有padding的话…就浪费了。
顺便演示一下基类AbstractList的情况吧:
$ java -XX:-UseCompressedOops -Djol.tryWithSudo=true -jar ~/Downloads/jol-cli-0.6-full.jar internals java.util.AbstractList
objc[49134]: Class JavaLaunchHelper is implemented in both /Users/krismo/sdk/jdk1.8.0_101/Contents/Home/bin/java and /Users/krismo/sdk/jdk1.8.0_101/Contents/Home/jre/lib/libinstrument.dylib. One of the two will be used. Which one is undefined.
# Running 64-bit HotSpot VM.
# Objects are 8 bytes aligned.
# Field sizes by type: 8, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 8, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
VM fails to invoke the default constructor, falling back to class-only introspection.
java.util.AbstractList object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 16 (object header) N/A
16 4 int AbstractList.modCount N/A
20 4 (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total