先说结论
仅看字符串对象本身占用的空间,在开启压缩指针的情况下,无论是 JDK8 还是 JDK8 以后,此字符串对象所占用的空间均为24个字节,但是针对版本的差异,具体的细节有所不同。在不开启压缩指针的情况下,均为32个字节。
如果加上对应的底层数组大小,为24 + 16 + n(压缩指针) 或者 32 + 24 + n(未开启压缩指针jdk8)或者 32 + 16 + n(未开启压缩指针jdk17)。其中n为实际使用的数组空间大小,取决于编码方式和长度。
接下来使用jol详细查看。测试代码如下
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.17</version>
</dependency>
public static void main(String[] args) {
String s = "abc";
System.out.println(ClassLayout.parseInstance(s).toPrintable());
}
JDK8--开启压缩指针
java.lang.String object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0x000016d0
12 4 char[] String.value [a, b, c]
16 4 int String.hash 0
20 4 (object alignment gap)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
- MarkWord。占用8个字节;
- 类型指针。占用4个字节;
- char数组的指针。占用4个字节;
- hashcode。占用4个字节;
- 对齐填充。占用4个字节。
对象的第三部分是对齐填充,这并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。由于HotSpot虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说就是任何对象的大小都必须是8字节的整数倍。对象头部分已经被精心设计成正好是8字节的倍数(1倍或者2倍),因此,如果对象实例数据部分没有对齐的话,就需要通过对齐填充来补全。 --《深入理解JVM虚拟机》
JDK17--开启压缩指针
java.lang.String object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0x0000ec18
12 4 int String.hash 0
16 1 byte String.coder 0
17 1 boolean String.hashIsZero false
18 2 (alignment/padding gap)
20 4 byte[] String.value [97, 98, 99]
Instance size: 24 bytes
Space losses: 2 bytes internal + 0 bytes external = 2 bytes total
- MarkWord。占用8个字节;
- 类型指针。占用4个字节;
- hashcode。占用4个字节;
- 编码类型。占用1个字节,其中值为0代表使用 LATIN1,1代表使用 UTF16;
- hashcode是否为0。默认为false,只有当调用
hashcode()方法的时候,计算出来的hashcode为0,才会设置为 true,占用1个字节; - byte数组的指针,占用4个字节。
默认情况下Java会使用压缩指针,当管理的内存大于32g时,会不使用压缩指针,此时在JDK8中,类型指针和对象指针会变为8字节,在JDK17中,类型指针仍然保持4字节,对象指针占用8字节,但是由于对齐填充的存在,两者占用空间仍然一致,为32字节。
关于数组占用空间的大小
通过上面的记录,我们还可以看出来,JDK8中,使用的 char数组,而在JDK17中,使用的byte数组,当我们的整个字符串都可以使用 LATIN1 字符集进行编码的时候,就会使用 LATIN1,每个字符只占用一字节,来减少空间的占用。
而真实的数组空间占用大小如下(使用JDK17)
[B object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0x00006848
12 4 (array length) 3
16 3 byte [B.<elements> N/A
19 5 (object alignment gap)
Instance size: 24 bytes
Space losses: 0 bytes internal + 5 bytes external = 5 bytes total
我们可以看到除开数据部分,占用空间为8+4+4=16字节,如果当前使用指针压缩,jdk8和jdk16对象头占用空间大小一致,但是在当前未使用指针压缩的情况下,jdk8中,类型指针会占用8字节,导致长度额外占用8字节,所以为24字节。
关于对齐填充
在 《深入理解JVM虚拟机》 这本书中,作者介绍到对象会存在对齐填充,而Java正是通过对齐填充,可以在使用压缩指针的情况下,最多管理32G的内存,这也解释了为什么当内存大于等于32G时,会不使用压缩指针。