String、StringBuffer、StringBuilder
String
final class
-
- 所有属性也都是 final 的
- 原生的保证了基础线程安全
- 因为无法对它内部数据进行任何修改
典型的 Immutable 类
-
- 所以拼接、裁剪字符串等动作,都会产生新的 String 对象
- 对应用性能有明显影响
- 操作不当可能会产生大量临时字符串
- 所以拼接、裁剪字符串等动作,都会产生新的 String 对象
StringBuffer
可修改字符序列
-
- StringBuffer 和 StringBuilder 底层都是利用可修改的(char,JDK 9 以后是 byte)数组
- 继承了 AbstractStringBuilder
把各种修改数据的方法都加上 synchronized 关键字
-
- 线程安全
- 也随之带来了额外的性能开销
- 简单粗暴
- 线程安全
StringBuilder
可修改字符序列
-
- 在能力上和 StringBuffer 基本相同
- 继承了 AbstractStringBuilder
Java 1.5 中新增
-
- 是绝大部分情况下进行字符串拼接的首选
线程不安全
-
- 有效减小了开销
内部数组
内部数组的大小应该如何决定呢?
目前的实现
-
- 构建时初始字符串长度加 16
- 如果构建对象时没有输入最初的字符串
- 初始值就是 16
- 可以在创建时,主动指定合适的大小
- 如果构建对象时没有输入最初的字符串
- 扩容会产生多重开销
- 要抛弃原有数组,创建新的数组,还要进行 arraycopy
- 构建时初始字符串长度加 16
String的演化
在历史版本中,使用 char 数组保存数据
-
- 非常直接
- Java 中的 char 是两个 bytes 大小
- 但拉丁语系语言的字符不需要太宽的 char
- 这样无区别的实现就产生了一定的浪费
Compact Strings
-
- 将数据存储方式从 char 数组改变为一个 byte 数组加上一个标识编码的所谓 coder,并且将相关字符串操作类都进行了修改
- 紧凑字符串
- 更小的内存占用、更快的操作速度
- 所有相关的 Intrinsic 之类也都进行了重写
- 保证没有任何性能损失。
- 这个特性对于绝大部分应用来说是透明的
- 因为虽然底层实现发生了改变,但 Java 字符串的行为并没有大的变化,
- 绝大部分情况不需要修改已有代码
字符串缓存
intern 机制
Intern 是一种显式地排重机制
-
- Java的intern机制是指在运行时,如果一个字符串常量(即用双引号括起来的字符串字面量)已经存在于字符串池中,那么在创建新的该字符串常量时,会返回已存在的字符串常量的引用,而不是新创建一个对象。
intern() 方法
-
- 作用是提示 JVM 把相应字符串缓存起来,以备重复使用。
- String 在 Java 6 以后提供
- 创建字符串对象并调用 intern() 方法时
- 如果字符串常量池中已经有缓存的字符串
- 返回缓存里的实例
- 如果没有
- 将此String对象添加到池中缓存起来
- 如果字符串常量池中已经有缓存的字符串
早期的版本保存在 PermGen中
-
- PermGen也就是"永久代"
- 这个空间是很有限的
- 基本不会被 FullGC 之外的垃圾收集照顾到
- 所以,如果使用不当,OOM 就会光顾。
后续版本中保存在堆中
-
- 避免永久代占满的问题
- 永久代在 JDK 8 中被 MetaSpace(元数据区)替代
- 默认缓存大小也在不断地扩大中
- -XX:+PrintStringTableStatistics
- 查看默认缓存
- -XX:StringTableSize=N
- 调整默认缓存
- -XX:+PrintStringTableStatistics
- 避免永久代占满的问题
G1 GC 下的字符串排重
在 Oracle JDK 8u20 之后,推出了这个新的特性
将相同数据的字符串指向同一份数据
-
- JVM 底层的改变,因此不需要修改 Java 类库
目前是默认关闭的
-
- -XX:+UseStringDeduplication
- 开启G1 GC 下的字符串排重功能
- -XX:+UseStringDeduplication
Intrinsic 机制
JVM 的底层优化机制
运行的往往就是特殊优化的本地代码,而不是 Java 代码生成的字节码
一种利用 native 方式 hard-coded 的逻辑
-XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining
-
- 查看 intrinsic 发生的状态