String类在内存中存储
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类。