三者区别
- String:java 非常基础和重要的类
提供构造、管理字符串各种基本逻辑。典型immutable类,被声明为final class,属性也是final,无法进行基础扩展
由于不可变性,类似拼接、裁剪字段,都会产生新的string对象,由于字符串操作普遍性,相关操作效率往往对应用新年有显著影响。
- StringBuffer:线程安全条件下解决字符串拼接、裁剪效率
为解决上面字符串操作拼接产生太多中间对象的问题而提供的一个类,可以使用append,add方法,将字符串添加已有序列的末尾,或指定位置
本质是线程安全的可修该字符序列,它保证线程安全,也随之带来额外性能开销。如多个线程将字符串拼成一个字符串序列,不能存在重复拼接
- StringBuiler 是java1.5后新增
能力和StringBuffer无本质区别,但是去掉线程安全部分,有效减小开销,是绝大多情况下字符串拼接首选。
深入考察
- String和相关类,考察基本的线程安全设计与实现,各种编程实践
- Immutable类:无法对其内部数据修改,访问对象,拷贝构造函数时原生保证基础线程安全,不需额外复制数据。
- StringBuffer线程安全,是通过各种修改数据的方法都加上synchronized关键,从JVM层次实现线程访问安全。
- 为了修改字符序列目的,StringBuffer,StringBuilder底层都是利用可修改的(char,jdk9以后是byte)数组,二者都继承AbstractStringBuiler,里面包含了基本操作,区别是字符序列操作方法是否加上synchronized关键字
- StringBuffer,StringBuilder的可修改数组初始长度是16(过大浪费空间;过小需要扩容,抛弃原有数组,创建新的数组,还要进行arraycopy),如果确定拼接发生多次及拼接长度,可以指定合适的大小,在new StringBuffer(100)/StringBuilder(100);
例子:
String strByBuilder = newStringBuilder().append("aa").append("bb").append("cc").append("dd").toString();
String strByConcat = "aa" + "bb" + "cc" + "dd";
final String abcFinal = "abc";
String str4 = abcFinal+"01"//final修饰的变量,在编译期间会自动进行常量替换
String str1 = "abc"; //str1指向字符串常量池的对象引用
String str2 = new String("abc"); //str2指向堆(new 的对象)中的对象引用,故不会等
String str3 = str2.intern(); //指向字符串常量池中对象引用因而str1==str3 ,会在常量池中查找是否存在相同的对象,若有不需创建,直接复制给新变量,否则创建
II>非静态拼接逻辑在jdk版本下的差异: jdk8 自动被javac转换为StringBuilder操作, jdk9 利用invokerDynamic将字符串拼接的优化与java生成的字节码解耦,假设未来JVM增强相关运行时实现,不需要依赖javac任何修改
III>日常编码:保证程序可读性,可维护性,比所谓最有性能更重要,可以根据实际需求酌情选择具体编码方式(append或"+")。
JVM 对象缓存机制的理解以及如何良好使用(字符串缓存)
-
常见应用进行堆转出(Dump Heap:将对象倒入heap空间),然后分析对象组成,发现相当一部分对象是字符串,其中一部分是重复。如果避免重复创建字符串,可以有效降低内存消耗和对象创建开销
-
String在java 6使用inter(),目的是提示JVM将相应字符串缓存起来,以便复用。在进行创建字符串并调用intern()方法时,如果已经有缓存的字符串,就会返回缓存中实例,否则将其缓存起来。
-
然而,java 6不推荐大量使用inter()方法,因为字符串对象时缓存在PermGen(永久代),该空间有限,不会被fullgc职位垃圾收集器收集到,所以使用不当会造成OOM.
-
后序版本,该缓存防止堆中,避免PermGen 被占满问题,甚至jdk8PermGen 被 MetaSpace替代了,默认大小从最初1009,到后来的60013.
-
可以用参数查看Metaspace大小 -XX:+PrintStringTableStatistics,也可以在必要情况下使用 -XX:StringTableSize=N 修改字符串缓存大小
-
Inter()是一种字符串缓存时的显式排重机制,但也有副作用
1.显式调用麻烦,2.难以保证效率,开发阶段很难预计字符串重复情况,进而产生是否有必要排重的问题
Oracle JDK 8u20小版本后,推出G1 GC下字符串排重,通过将相同数据的字符串执行同一份数据做到,是JVM底层改变,不需java类库作修改
开启参数 -XX:+UseStringDeduplication
- 运行时,字符串的一些基础操作会直接利用JVM内部Intrinsic,往往运行的是特殊优化的本地代码,而非java代码生成的字节码,是利用native方式hard-code的垃圾,
算是一种特别内联,很多优化需要直接使用特定CPU指令,可以在启动实验应用时设置下面参数,了解intrinsic发生状态
-XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining
//样例输出片段
180 3 3 java.lang.String::charAt (25 bytes)
@ 1 java.lang.String::isLatin1 (19 bytes)
...
@ 7 java.lang.StringUTF16::getChar (60 bytes) intrinsic
JVM优化java代码技巧
- String相关类的演进,如java 9实现巨大变化
-
java9之前使用插入数组保存拼接、裁剪的字符串
-
java9后用byte 数组。因为一个char是两个byte大小,拉丁语系语言的字符,不需要太宽char,造成浪费。
并且引入Compact String设计,将char 数组改为 byte数组加上一个编码coder,且保证相关字符串操作了都进行了修改,
所有相关intrinsic之类都进行重写,以保证无任何性能损失。 -
底层发现巨大变化,但java字符串编码无较大变化,所以特性对大部分应用透明。但在极端情况下,字符串会出现退化,如字符串大小,
原来char数组,字符串最大长度即数组本身长度闲置,但是byte数组,同样数组长度,存储能力下降一倍。
-