Java String(JVM角度)

导读:本篇文章讲解 Java String(JVM角度),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

基本特性

存储结构变更

  1. jdk8及之前的jdk版本中,String的内存存储结构是char[]字符数组,但是在Jdk9及之后改成了byte[]字节数组。
  • 原因是,堆空间中大部分的字符串内容都是latin字符,基本上一个byte就可以表示,但是char占两个字节,导致一半的内存浪费得不到合理使用。
  • 对于需要两个字节表示的字符串,String中添加了一个字符编码集标识,如果是lation/ISO-8859-1就使用一个byte去存在,gbk等字符集使用两个字节去存储。
  • 对于和String相关的类也做了修改,使用byte[]去保存数据,比如StringBuffer,StringBuilder等等。
  • 结论:String再也不要char[]来存储啦,改成了使用byte[]加上编码标记的方式,节约了一下空间。

不可变性

  1. 当对字符串重新赋值时,需要重写指定内存区域的赋值,不能使用原有的value进行赋值。
  2. 当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
  3. 当调用String的replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
  4. 通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中。
  5. 字符串常量池在Jdk7之后存放在堆空间,字符串常量池不能存储在相同的字符串。如果不同的变量指向相等的字符串字面量,那么它们指向相同的字符串。
  6. 每个类都有一个运行时常量池,和每个类的字节码文件一致,只不过一个是静态的一个加载到内存中的,供运行时调用。

字符串常量池中是不会存储相同内容的字符串的

因为字符串常量池底层的存储结构是HashTable,键肯定不能重复,键就是字符串内容。

  1. String的String pool是一个固定大小的Hashtable,默认值大小长度是1009。如果放进String Pool的String非常多,就会造成Hash冲突严重,从而导致链表会很长,而链表常量以后直接会造成的影响就是当调用String.intern是性能大幅下降。
  2. 使用-XX:StringTableSize可设置StringTable的长度。
  3. 在Jdk6中StringTable是固定的,就是1009的长度,所有如果常量池中的字符串过多就会导致效率下降很快。StringTableSize设置没有要求。
    jdk6中StringTable的长度
    在这里插入图片描述
  4. 在jdk7中,StringTable的长度默认值是60013,jdk8开始1009是StringTable可设置的最小值。

String的内存分配

  1. 在java语言中有8种基本数据类型和一种比较特殊的类型String。这些类型为了使它们在运行过程中速度更快、更节省内存,都提供了一种常量池的概念。
  2. 常量池就类似于一个java系统级别提供的缓存。8种基本类型的常量池都是系统协调的,String类型的常量池比较特殊。它的主要使用方法有两种。
  • 直接使用双引号声明出来的String对象会直接存储在常量池中。
    比如:String info = “hello”;
  • 如果不是使用双引号声明的String对象,可以使用String提供的intern()方法。
  1. Java6及以前,字符串常量池存放在永久代。
  2. java7中oracle的工程师对字符串池的逻辑做了很大的改变,即将字符串常量池的位置调整到Java堆内。
  • 所有的字符串都保存在堆(Heap)中,和其他普通对象一样,这样可以让你在进行调优应用是仅需要调整堆大小就可以了。
  • 字符串常量池概念原本使用的比较多,但是这个改动使我们有足够的理由让我们重新考虑在java7中使用String.intern()。
  1. Java8元空间,字符串常量也在堆中。
    在这里插入图片描述
    在这里插入图片描述
  2. StringTable为什么要调整
  • permsize(永久代)通常默认比较小,如果大量的存放大量的字符串,会很容易导致OOM。
  • 永久代垃圾回收频率低,不能及时清除不用的字符串,大量字符串会很容易导致Full GC,降低程序性能,同时又容易导致OOM。

字符串拼接操作

  1. 常量与常量的拼接结果在常量池,原理是编译期优化(编译成字节码后就已经直接拼接好结果,放在常量池,运行时从常量池中获取)。
    在这里插入图片描述
    字节码文件反编译的结果如下,说明编译期就已经优化成同一个字面量。
    在这里插入图片描述
  2. 常量池中不会存在相同内容的常量,因为底层类似hashMap结构,以字符串做key值,导致无法重复。
  3. 只要其中有一个变量(非final的变量),结果就在堆中(堆中非常量池区域)。变量拼接的原理是StringBuilder(拼接参数中如果出现一个变量,就会生成StringBuilder对象,通过append的方式拼接字符串)。
    在这里插入图片描述在这里插入图片描述
    在这里插入图片描述
    但如果是常量就是不是创建StringBuilder追加的方式
    在这里插入图片描述
  4. 如果拼接的结果调用intern()方法,则主动将常量池中还没有的字符串对象放入池中,并返回对象地址。
    在这里插入图片描述

字符串变量拼接和StringBuilder方式比较

==================================================耗时4014ms========================
String str = "";
for(int i = 0; i < 10000; i++) {
	str += i;
}
==================================================耗时7ms===========================
StringBuilder str = new StringBuilder();
for(int i = 0; i < 10000; i++) {
	str.append(i);
}
  1. 效率
    通过StringBuilder的append()的方式添加字符串的效率要远远高于String字符串拼接的方式。
  2. 详情
  • StringBuilder的append()的方式:至始至终只会创建一个StringBuilder的对象;使用String的字符串拼接方式(因为包含变量每次拼接都会创建StringBuilder,String对象):创建多个StringBuilder和String对象。
  • 使用String的字符串拼接方式:内存中由于创建了较多的StringBuilder和String对象,内存占用更大;如果进行GC,需要额外的时间。
  1. StringBuilder追加字符串优化空间
    在实际开发中,如果基本确定前前后后的字符串长度不超过某个限定值highLevel的情况下,建议使用代参构造器创建对象:
    StringBuilder s = new StringBuilder(highLevel);
    备注:相当于底层创建一个限定长度的数组 new char[highLevel],不会因为长度不够不同的拷贝复制生成新数组扩展,导致性能降低。

intern的使用

面试题

  1. String str = new String(“ab”);生成几个对象
    答:两个,分别是堆中的new String()和字符串常量池中的“ab”,可以通过查看字节码佐证(需要从常量池中载入”ab”字符串,并初始化new String()对象)。
    在这里插入图片描述
    在这里插入图片描述
  2. String str = new String(“a”) + new String(“b”);生成几个对象?
    答:6个对象,具体对象如下:
    因为涉及字符串的拼接,需要生成StringBuilder()对象。
    每个new String(“xxx”)需要两个对象,一个堆中,一个字符串常量池中。
    StringBuilder.toString()会new String()对象,但是通过查看字节码发现toString()不会在从字符串常量池中获取字符串值,说明不会在字符串常量池中生成对象。
    在这里插入图片描述
  3. 面试题之intern
    在这里插入图片描述
  • 代码流程图
    在这里插入图片描述
    在这里插入图片描述

intern总结

  1. jdk6中,将这个字符串对象尝试放入字符串常量池中。
  • 如果池中有,则并不会放入。返回已有的池中的对象的地址。
  • 如果没有,会把调用Intern的String对象复制一份,放入常量池中,并返回池中的对象地址。
    在这里插入图片描述
  1. jdk7起,将这个字符串对象尝试放入字符串常量池中。
  • 如果池中有,则并不会放入。返回已有的池中的对象的地址。
  • 如果没有,则会把调用intern的String对象的引用地址复制一份,放入池中(池中存放的是堆中String对象的地址),并返回池中的引用地址(堆中String对象的地址)。
    在这里插入图片描述

练习

在这里插入图片描述
在这里插入图片描述

使用intern的优点

  1. 如果存在大量相同的字符串,使用intern会节约内存空间。
  • String s = new String(“xxx”); // 堆空间和常量池都会创建一个对象,但返回的是堆空间的对象地址,如果有大量相同的字符串,那么内存中会维护大量的堆空间中对象的地址,造成空间浪费。
  • String s = new String(“xxx”).intern(); // 堆空间和常量池都会创建一个对象,但是返回的是常量池中对象的地址,那么堆空间的字符串对象地址就不需要维护,会被MinorGC即时清除,又因为常量池中不会有相同的字符串,那么空间就会得到很大程度的节省。
  • 如果存在大量相同的字符串,使用intern会提高程序执行速度,提高效率。

StringTable垃圾回收

  1. 测试代码
    在这里插入图片描述
    执行代码,可以看到有GC回收出现,同时StringTable中的字符串条目小于100000,证明StringTable中存在垃圾回收行为。
    在这里插入图片描述
    在这里插入图片描述

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/100423.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!