📚 残梦三生

记录技术成长,分享学习心得

← 返回首页

String类在内存中存储

分类:Java | 日期:2024-12-04

1 简介

"Hello World"这个词大家一定非常熟悉,在学习编程时,许多人学会的第一个程序应该就是输出这么一句话,是大家梦开始的地方。而对于程序来说,"Hello World"是一个字符串,是String类,需要保存在内存中,接下来就来看一下他是如何进行保存的。

2 存储位置

在JDK1.8中,JVM将常量池转移到堆中,把堆分成了两部分,一部分用于保存字符串常量池,一部分用于存储对象。

String a = "Hello World";
String b = new String("Hello World");

如图2-1,并结合上面两句代码可以看出,a是直接指向了常量池中的"Hello World",而b是先在堆里面创建了一个"new String",然后再指向了常量池中的"Hello World"。

当一个字符串内容不存在时,可以使用String的intern方法来创建。这个方法的作用是,如果当前字符串内容存在于字符串常量池,就直接返回字符串常量池中的字符串;否则就将这个String对象添加到常量池中,并返回String对象的引用。

3 String类的实现

到目前为止,String类的实现共经历了三次变化。

3.1 Java6以及之前的版本

3.1.1 实现方法

String的实现是通过对char数组进行了封装,通过offset偏移来确定char数组的起始位置、count字符数量确定这个字符串的长度,这种设计允许String对象通过共享底层字符数组来实现字符串的切片或子字符串操作。

通过hash来优化字符串比较性能。hash字段用于存储hashCode()方法计算出来的值,通过缓存哈希码来避免重复计算,从而提高性能。

3.1.2 存在的问题

内存消耗。每个String对象都需要持有一个char[]数组,当大量的短字符串出现时,可能出现char[]数组本身的内存消耗远大于真实数据的消耗。

性能消耗。在Java6及之前的版本中,字符串的操作不会创建新的char[]数组,而是共享原有数组的部分内容,这看似是节省内存的操作。但如果子字符串操作频繁,可能会造成内存泄漏的问题出现。由于数组共享,既是子字符串不再被需要,底层数组依旧还是会被保持在内存中无法删除;同时,由于子字符串内容相似,还会造成hash冲突问题。

3.2 Java7/8

3.2.1 结构优化

在 Java7和Java8中,String类取消了offset和count两个变量。只保留了char[]数组和hash。这一改动简化了类的实现,减少了内存开销,提高了字符串操作的性能,同时也使代码维护变得更加简单。

3.2.2 存在的问题

性能问题。当处理非常大的字符串或者进行大量子字符串操作,由于缺少了offset和count两个变量,所有的操作都必须通过新建一个String对象来复制这些字符,这样会增加内存的使用,并且降低性能。

3.3 Java9

3.3.1 结构优化

Java9中,引入了byte[]数组,增加了coder字段以及字符编码。使得字符串的内存管理更加灵活和高效。coder变量帮助了Java9动态决定是否使用byte[]数组还是char[]数组来存储字符串数据。

3.3.2 优化方式

byte[]数组:在Java9中,将char[]数组改成了byte[]数组,这种改动在遇到一个字符串仅包含ASCII字符的时候,每个字符仅占用1字节,而不是char[]数组的2字节,可以减少内存的占用。

coder字段:coder字段主要是记录字符串的编码方式,例如UTF-8、UTF-16、Latin-1、ASCII等。这个字段的引入允许Java动态的选择使用byte[]还是char[]来存储字符串数据。

4 String内存占用

上面说了String的实现方法的演变,接下来就看一下在Java9中,"Hello World" 究竟会占用多少的内存呢。

4.1 Java对象组成

在虚拟机中,一个java对象是由三部分组成的,分别是对象头、实例数据和对齐填充。

对象头包含两部分内容,分别是Mark Word和Class对象指针,每个部分各占4个字节,一共8字节。

实例数据,里面放置了对象的成员变量,按照成员变量的数据类型来决定大小,其中reference是4字节。

对齐填充,确保最后对象长度为8的倍数。

4.2 String类长度

这里还是以"Hello World"为例,String类的头部信息一共是8个字节;实例数据中,String类属于接口类型,长度为4个字节,coder字段占1个字节,补齐3个字节,整个空的String一共为16字节。

接口所引用的是byte[]数组,byte[]数组中,Mark World占4字节,指针占4字节,因为是数据,所以还有个数组长度4字节,所以对象头为12字节,"Hello World"的数组长度为11个byte,共11个字节,补齐1个字节,共占24个字节;

所以"Hello World"的总长度为:16+24=40字节。

5 总结

String是一个常用的类,这里主要讲述了String类在内存中的存储方法,并且通过对比Java不同的版本,看其底层是如何设计,各自都存在有什么样的问题,并且了解每一个版本调整的原因和方式,最后还介绍了"Hello World"在内存中多占用的内存大小,方便更好的理解String类。