Java - 底层建筑 - JVM - 第3篇 - StringTable

String的基本特性

  • String:字符串,使用一对 “” 引起来表示

String s1 = “hello”;
String s2 = new String(“hello”);

  • String:声明为final的,不可被继承

  • String 实现了Serializable接口:表示字符串是支持序列化的,实现了Comparable接口:表示String可以比较大小

  • 在JDK8以及之前,String内部定义了final char[] value 用来存储字符串数据,JDK9之后使用 byte[]

  • String:代表不可变的字符序列。简称:不可变性

    1. 当对字符串重新赋值的时候,需要重写指定内存区域赋值,而不是修改原有的value进行赋值
    2. 不能对现有的字符串进行连接操作,也需要重新指定内存区域,不是修改原有的value进行赋值
    3. 当调用String的replace()方法修改指定字符或者字符串的时候,也需要重新指定内存区域赋值,不能使用原有的value赋值
    4. 通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串声明在常量池中

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

  • String的String Pool 是一个固定大小的HashTable 默认长度为1009,如果放进去String Pool 的String非常多,就会造成Hash冲突严重,从而导致链表很长,而链表长了之后,会直接影响调用 String.intern()的性能
  • 使用 -XX:StringTableSize 可设置StringTable的长度
  • 在JDK6中的StringTable是固定的,就是1009的长度,如果字符串常量池中的字符串过多就会导致效率下降很快,StirngTableSize设置没有要求
  • 在JDK7中,StringTable的默认长度为 60013,JDK7没有要求
  • JDK8开始1009 是可设置的最小值
  • Stirng intern()方法:如果字符串常量池中没有对应的字符串的话,就添加在常量池

String的内存分配

  • 在Java语言中有8种基本数据类型和String,这些类型为了使它们在运行速度中更快,更节省内存。都提供了常量池

  • 常量池就相似于一个Java系统级别提供的缓存。8中基本类型都是系统协调的,String类型的常量池比较特殊,主要的使用方法有2种

    1. 直接使用双引号声明String的对象会直接存储在常量池中
    2. 如果不是使用双引号声明的String对象,可以使用String提供的intern() 方法
  • 在JDK6以及以前,字符串常量池存放在永久代

  • JDK7种Oracle对字符串池的逻辑做了很大的改变,将 字符串常量池的位置调整到Java堆内

    1. 所有的字符串都保存在堆中,和其他普通对象一样,这样可以让你在进行调优的时候仅仅需要调整堆大小就可以了
    2. 字符串常量池的概念原本使用的比较多,但是这个改动使得我们有足够的理由考虑使用String.intern();
    3. Java8元空间,字符串常量在堆

StringTable为什么需要调整?

  1. 之前的永久代比较小,放大量的字符串,会占用很大的空间
  2. 永久代垃圾回收的频率很低

`

字符串的拼接操作

  • 常量与常量的拼接结果在常量池,原理是编译期优化
  • 常量池中不会存在相同内容的常量
  • 只要其中有一个是变量,结果就在堆中。变量拼接的原理是StringBuilder
  • 如果拼接的结果调用intern()方法,则主动将常量池中还没有的字符串对象放入池中,并返回此对象的地址

拼接操作的原理

  • 实例代码
package cn.icanci.jvm.string;/*** @Author: icanci*/
public class CreateStringDemo {public static void main(String[] args) {String str = "hello ";String str2 = "world";String s = str + str2;System.out.println(s);}
}
  • javap 反编译之后的字节码
 0 ldc #2 <hello >2 astore_13 ldc #3 <world>5 astore_26 new #4 <java/lang/StringBuilder>9 dup
10 invokespecial #5 <java/lang/StringBuilder.<init>>
13 aload_1
14 invokevirtual #6 <java/lang/StringBuilder.append>
17 aload_2
18 invokevirtual #6 <java/lang/StringBuilder.append>
21 invokevirtual #7 <java/lang/StringBuilder.toString>
24 astore_3
25 getstatic #8 <java/lang/System.out>
28 aload_3
29 invokevirtual #9 <java/io/PrintStream.println>
32 return

String拼接的原理
只要是变量,就先创建一个StringBuilder对象,然后调用StringBuilder的append()方法,分别拼接两个字符串,拼接结束之后,调用StringBuilder的toString()方法,返回为新的字符串

  • 在JDK5.0之前使用的是StringBuffer进行拼接,在JDK5.0之后,StringBuilder出现,然后使用了StringBuilder来拼接
  • 拼接的实际操作如下
StringBuilder sb = new StringBuilder();
sb.append("hello ");
sb.append("world");
sb.toString(); // 类似于 new String("hello world");
  • 字符串拼接操作,不一定是使用的是StringBuilder,如果两边都是final的字符串常量或者常量引用,仍然使用编译器优化,此时不使用StringBuilder的方式

  • 针对final修饰类、方法、基本数据类型、引用数据类型的量的结果的时候,能使用fianl就使用final

  • 在使用StringBuilder和StringBuffer的时候,如果已知具体的大小,可以进行有参构造函数,创建一个指定初始大小的数组,可以避免返回扩容转移数组,耗费空间和资源

intern() 的使用

  • 如果不是双引号声明的String对象,可以使用String提供的intern方法:intern方法会从字符串常量池中查询当前字符串是否存在,若不存在,就会将当前字符串放入常量池中

String s = new String(“爪哇贡尘拾Miraitow”).intern();

  • 也就是说,如果在任意字符串上调用String intern方法,那么其返回结果所指向的哪个类实例,必须和直接以常量形式出现的字符串实例完全相同。因此,下列表达式的值必为true

    • (“a”+“b”+“c”).intern() == “abc”
  • 通俗点讲,Interned String 就是确保字符串在内存中只有一份拷贝,这样可以节约内存空间,加快字符串操作任务的执行速度。注意:这个值会被存放在字符串内部池(String Intern Pool)

  • 只要字符串对象调用了 intern() 方法,那么返回值是指向字符串常量池中的数据

  • 实例代码

package cn.icanci.jvm.string;/*** @Author: icanci*/
public class StringInternDemo {public static void main(String[] args) {String s = new String("1");s.intern();String s2 = new String("1");System.out.println(s == s2);String s3 = new String("1") + new String("2");s3.intern();String s4 = "11";System.out.println(s3 == s4);}
}// 打印结果
// false
// false
  • 题目
  • new String(“ab”) 会创建几个对象?
package cn.icanci.jvm.string;/*** @Author: icanci*/
public class StringNewDemo {public static void main(String[] args) {String ab = new String("ab");}
}
 0 new #2 <java/lang/String>3 dup4 ldc #3 <ab>6 invokespecial #4 <java/lang/String.<init>>9 astore_1
10 return
  • 从上面反编译看出,创建了两个对象,一个放在堆中,一个放在了字符串常量池 ldc指令

  • 如果之前常量池是有的,就字符串的值就直接引用到此常量

  • new String(“a”) + new String(“b”) 创建了几个对象

package cn.icanci.jvm.string;/*** @Author: icanci*/
public class StringNewDemo {public static void main(String[] args) {String ab = new String("b") + new String("b");}
}
 0 new #2 <java/lang/StringBuilder>3 dup4 invokespecial #3 <java/lang/StringBuilder.<init>>7 new #4 <java/lang/String>
10 dup
11 ldc #5 <b>
13 invokespecial #6 <java/lang/String.<init>>
16 invokevirtual #7 <java/lang/StringBuilder.append>
19 new #4 <java/lang/String>
22 dup
23 ldc #5 <b>
25 invokespecial #6 <java/lang/String.<init>>
28 invokevirtual #7 <java/lang/StringBuilder.append>
31 invokevirtual #8 <java/lang/StringBuilder.toString>
34 astore_1
35 return
  • 创建了哪些对象

    • new StringBuilder()
    • new String(“a”)
    • 常量池中的 “a”
    • new String(“b”)
    • 常量池中的 “b”
  • 深入刨析StringBuilderd的toString()方法

    • 对象 new String(“ab”)
    • 注意:toString()方法的调用,在字符串常量池中没有生成 "ab"
  • 实例代码

package cn.icanci.jvm.string;/*** @Author: icanci*/
public class StringInternDemo {public static void main(String[] args) {String s = new String("1");// 调用此方法之前,字符串常量池已经有了 "1"s.intern();// 此时 s 指的是堆空间的地址// 此时 s2 指向的是常量池中的地址String s2 = "1";// JDK6:false// JDK7/8:falseSystem.out.println(s == s2);// 因为 new String("1") + new String("1"); 这个过程没有把"11"放在常量池// 此时 s3 的地址就是相当于 new String("11"); 字符串常量池没有"11"String s3 = new String("1") + new String("1");// 在字符串常量池生成"11"// 这个"11"如何理解// 在JDK6中就是创建了一个新的对象// 在JDK7中,调用 s3.intern(),字符串常量池中 "11"的值就是创建对象的地址s3.intern();// 使用的是上一行代码执行时,在常量池中生成的"11" 的地址String s4 = "11";// JDK6:false// JDK7/8:trueSystem.out.println(s3 == s4);}
}
  • 反编译
 0 new #2 <java/lang/String>3 dup4 ldc #3 <1>6 invokespecial #4 <java/lang/String.<init>>9 astore_1
10 aload_1
11 invokevirtual #5 <java/lang/String.intern>
14 pop
15 ldc #3 <1>
17 astore_2
18 getstatic #6 <java/lang/System.out>
21 aload_1
22 aload_2
23 if_acmpne 30 (+7)
26 iconst_1
27 goto 31 (+4)
30 iconst_0
31 invokevirtual #7 <java/io/PrintStream.println>
34 new #8 <java/lang/StringBuilder>
37 dup
38 invokespecial #9 <java/lang/StringBuilder.<init>>
41 new #2 <java/lang/String>
44 dup
45 ldc #3 <1>
47 invokespecial #4 <java/lang/String.<init>>
50 invokevirtual #10 <java/lang/StringBuilder.append>
53 new #2 <java/lang/String>
56 dup
57 ldc #3 <1>
59 invokespecial #4 <java/lang/String.<init>>
62 invokevirtual #10 <java/lang/StringBuilder.append>
65 invokevirtual #11 <java/lang/StringBuilder.toString>
68 astore_3
69 aload_3
70 invokevirtual #5 <java/lang/String.intern>
73 pop
74 ldc #12 <11>
76 astore 4
78 getstatic #6 <java/lang/System.out>
81 aload_3
82 aload 4
84 if_acmpne 91 (+7)
87 iconst_1
88 goto 92 (+4)
91 iconst_0
92 invokevirtual #7 <java/io/PrintStream.println>
95 return

  • 练习
package cn.icanci.jvm.string;/*** @Author: icanci*/
public class StringInternDemo2 {public static void main(String[] args) {// 拓展// 执行完之后,字符串常量池不存在 "11" String s3 = new String("1") + new String("1");// 在字符串常量池生成 "11"String s4 = "11";String s5 = s3.intern();System.out.println(s3 == s4);System.out.println(s4 == s5);}
}// 打印
// false
// true

总结

  • jdk6中,将这个字符串对象尝试放入串池

    • 如果串池中有,则不会放入,返回已有的串池中的对象的地址
    • 如果没有,会把此对象复制一份,放入串池,并返回串池中的对象地址
  • jdk7中,将这个字符串对象尝试放入串池
    • 如果串池中有,则并不会放入。返回已有的串池中的对象的地址
    • 如果没有,会把此对象的引用地址复制一份,放入串池,并返回串池中引用地址地址

Java-底层建筑-JVM-第3篇-StringTable相关推荐

  1. 旷野徒奔-Java底层篇(1.4)

    Java底层篇(1.4) 1.JVM → Java 对象模型 oop-klass.对象头 → HotSpot 即时编译器.编译优化 → 虚拟机性能监控与故障处理工具 jps, jstack, jmap ...

  2. 旷野徒奔-Java底层篇(1.3)

    Java底层篇(1.3) 1.JVM → Java 内存模型 计算机内存模型.缓存一致性.MESI 协议 可见性.原子性.有序性.happens-before. → JVM 参数及调优 -Xmx.-X ...

  3. java 底层ppt_阿里P7大牛,深入剖析JVM底层设计原理+高级特性pdf,附46页ppt

    前言 JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的. 引入J ...

  4. 《深入理解java虚拟机》(Jvm)一篇概括

    目录 1.Java内存区域与内存溢出异常 2.垃圾收集器与内存分配策略 3.虚拟机性能监控与故障处理工具 4.调优案例分析与实战 5.类文件结构 6.虚拟机类加载机制 7.虚拟机字节码执行引擎 8.类 ...

  5. 【JAVA进阶】JVM第二篇- JVM 垃圾回收详解

    写在前面的话 脑子是个好东西,可惜的是一直没有搞懂脑子的内存删除机制是什么,所以啊,入行多年,零零散散的文章看了无数,却总是学习了很多也忘了很多. 痛定思痛的我决定从今天开始系统的梳理下知识架构,记录 ...

  6. Java虚拟机(JVM)参数配置说明

    http://lavasoft.blog.51cto.com/62575/25492/ Java虚拟机(JVM)参数配置说明   在Java.J2EE大型应用中,JVM非标准参数的配置直接关系到整个系 ...

  7. java jvm调优_(第2部分,共3部分):有关性能调优,Java中的JVM,GC,Mechanical Sympathy等的文章和视频的摘要...

    java jvm调优 这是以前的文章(第3部分,共1部分)的继续:有关性能调优,Java中的JVM,GC,Mechanical Sympathy等的文章和视频的提要 . 事不宜迟,让我们开始使用我们的 ...

  8. (第2部分,共3部分):有关性能调优,Java中的JVM,GC,Mechanical Sympathy等的文章和视频的摘要...

    这是以前的文章(第3部分,共1部分)的继续:有关性能调优,Java中的JVM,GC,Mechanical Sympathy等的文章和视频的提要 . 事不宜迟,让我们开始使用我们的下一组博客和视频,印章 ...

  9. java 垃圾回收之垃圾回收器篇

    java 垃圾回收之垃圾回收器篇 一,垃圾回收器的分类与性能指标 GC分类 按线程数分(垃圾回收线程数 :可以分为串行垃圾回收器和并行垃圾回收器 串行回收指的是在同一时间段内只允许有一个CPU用于执行 ...

最新文章

  1. 推荐一个在线json数据格式化网站
  2. [洛谷P4726]【模板】多项式指数函数
  3. Firefox开发者工具里查看HTML元素的Box模型
  4. 【转】深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第一节 理解堆与栈
  5. java大数模板_java大数模板
  6. Vue (响应式原理-模拟-2-Observer)
  7. 360智能工程中心期待你的加入
  8. 值得看的hadoop书籍
  9. c# log4net
  10. 发字的楷书写法图片_想要不侵权?收下这篇可能是最全面的书法字设计指南!...
  11. VC++2010安装教程
  12. 数字图像处理100问—27 双三次插值( Bicubic Interpolation )
  13. AtCoder Beginner Contest 240 C
  14. qcloud.login 登录失败,可能是网络错误或者服务器发生异常的多种解决方法
  15. 敏捷转型行动笔记:内部敏捷教练培训资料分享——升华篇(持续学习与系统思考,成为好教练)
  16. 小程序战局最新动态!这些你应该知道
  17. Django+Vue开发生鲜电商平台之2.开发环境搭建
  18. 《计算机工程》投稿经验
  19. 树莓派使用排线摄像头和远程视频监控
  20. 领导看了会炸毛的溢出理论

热门文章

  1. 大数据清洗与预算第三章
  2. 6_linux软件的安装
  3. win7虚拟机时间不能修改怎么办
  4. 飞机的纵•横向运动简化数学模型及控制系统设计
  5. 计算广告-商业化体系
  6. 冰桶挑战引来了百度搜索冰桶算法
  7. 最强代码阅读器sourceInsight工具
  8. 修炼“七字诀”的小米——读《小米创业思考》
  9. java获取下周一_java 获取下周一日期
  10. Win系统 - 关于GPU,你需要长的“姿势”