Java 中一个字符串对象占用的空间

108 阅读4分钟

先说结论

仅看字符串对象本身占用的空间,在开启压缩指针的情况下,无论是 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
  1. MarkWord。占用8个字节;
  2. 类型指针。占用4个字节;
  3. char数组的指针。占用4个字节;
  4. hashcode。占用4个字节;
  5. 对齐填充。占用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
  1. MarkWord。占用8个字节;
  2. 类型指针。占用4个字节;
  3. hashcode。占用4个字节;
  4. 编码类型。占用1个字节,其中值为0代表使用 LATIN1,1代表使用 UTF16
  5. hashcode是否为0。默认为false,只有当调用 hashcode() 方法的时候,计算出来的hashcode为0,才会设置为 true,占用1个字节;
  6. 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时,会不使用压缩指针。