头条面试官:String、StringBuffer、StringBuilder的区别(下)

哈喽~ 又到了美丽的周五,而我仍然是忙碌工作的小姐姐。

这两天看到最热的新闻要数这一条:

6000万牵手抖音,罗永浩开启直播带货

头条面试官:String、StringBuffer、StringBuilder的区别(下)

好久没见老罗的身影,再一出现就变成了“带货达人”?


虽然还不知道这次他能不能成功,如今电商直播平台那么多,他却独挑抖音,相信还是看中其粉丝经济,侧面也说明头条旗下产品的实力。


那今天我还是接着上期的头条面试考点继续延伸,老规矩,这次更新的实践干货!


01


字符串设计和实现考量


头条面试官:String、StringBuffer、StringBuilder的区别(下)

头条面试官:String、StringBuffer、StringBuilder的区别(下)

头条面试官:String、StringBuffer、StringBuilder的区别(下)


其实,在通常情况下,没有必要过于担心,要相信Java还是非常智能的。


我们来做个实验,把下面一段代码,利用不同版本的JDK编译,然后再反编译,例如:

头条面试官:String、StringBuffer、StringBuilder的区别(下)


先编译再反编译,比如使用JDK 9:

头条面试官:String、StringBuffer、StringBuilder的区别(下)


JDK 8的输出片段是:

头条面试官:String、StringBuffer、StringBuilder的区别(下)


而在JDK 9中,反编译的结果就非常简单了,片段是:

头条面试官:String、StringBuffer、StringBuilder的区别(下)


你可以看到,在JDK 8中,字符串拼接操作会自动被javac转换为StringBuilder操作,而在JDK 9里面则是因为Java 9为了更加统一字符串操作优化,提供了StringConcatFactory,作为一个统一的入口。javac自动生成的代码,虽然未必是最优化的,但普通场景也足够了

,你可以酌情选择。


02


字符串缓存


我们粗略统计过,把常见应用进行堆转储(Dump Heap),然后分析对象组成,会发现平均25%的对象是字符串,并且其中约半数是重复的。如果能避免创建重复字符串,可以有效降低内存消耗和对象创建开销。


String在Java 6以后提供了intern()方法,目的是提示JVM把相应字符串缓存起来,以备重复使用。在我们创建字符串对象并调用intern()方法的时候,如果已经有缓存的字符串,就会返回缓存里的实例,否则将其缓存起来。一般来说,JVM会将所有的类似“abc”这样的文本字符串,或者字符串常量之类缓存起来。


看起来很不错是吧?但实际情况估计会让你大跌眼镜。


一般使用Java 6这种历史版本,并不推荐大量使用intern,为什么呢?魔鬼存在于细节中,被缓存的字符串是存在所谓PermGen里的,也就是臭名昭著的“永久代”,这个空间是很有限的,也基本不会被FullGC之外的垃圾收集照顾到。所以,如果使用不当,OOM就会光顾。


在后续版本中,这个缓存被放置在堆中,这样就极大避免了永久代占满的问题,甚至永久代在JDK 8中被MetaSpace(元数据区)替代了。而且,默认缓存大小也在不断地扩大中, 从最初的1009,到7u40以后被修改为60013。


你可以使用下面的参数直接打印具体数字,可以拿自己的JDK立刻试验一下。

头条面试官:String、StringBuffer、StringBuilder的区别(下)


你也可以使用下面的JVM参数手动调整大小,但是绝大部分情况下并不需要调整,除非你确定它的大小已经影响了操作效率。

头条面试官:String、StringBuffer、StringBuilder的区别(下)


Intern是一种显式地排重机制,但是它也有一定的副作用,因为需要开发者写代码时明确调用,

一是不方便,每一个都显式调用是非常麻烦的;另外就是我们很难保证效率,应用开发阶段很难清楚地预计字符串的重复情况,有人认为这是一种污染代码的实践。


幸好在Oracle JDK 8u20之后,推出了一个新的特性,也就是G1 GC下的字符串排重。它是通过将相同数据的字符串指向同一份数据来做到的,是JVM底层的改变,并不需要Java类库做什么修改。


注意这个功能目前是默认关闭的,你需要使用下面参数开启,并且记得指定使用G1 GC:

头条面试官:String、StringBuffer、StringBuilder的区别(下)


前面说到的几个方面,只是Java底层对字符串各种优化的一角,在运行时,字符串的一些基础操作会直接利用JVM内部的Intrinsic机制,往往运行的就是特殊优化的本地代码,而根本就不是Java代码生成的字节码。


Intrinsic可以简单理解为,是一种利用native方式hard-coded的逻辑,算是一种特别的内联,很多优化还是需要直接使用特定的CPU指令,具体可以看相关源码,搜索“string”以查找相关Intrinsic定义。当然,你也可以在启动实验应用时,使用下面参数,了解intrinsic发生的状态。

头条面试官:String、StringBuffer、StringBuilder的区别(下)

可以看出,仅仅是字符串一个实现,就需要Java平台工程师和科学家付出如此大且默默无闻的努力,我们得到的很多便利都是来源于此。


03


String自身的演化


头条面试官:String、StringBuffer、StringBuilder的区别(下)

头条面试官:String、StringBuffer、StringBuilder的区别(下)


04


小结


今天我从String、StringBuffer和StringBuilder的主要设计和实现特点开始,分析了字符串缓存的intern机制、非代码侵入性的虚拟机层面排重、Java 9中紧凑字符的改进,并且初步接触了JVM的底层优化机制intrinsic。


从实践的角度,不管是Compact Strings还是底层intrinsic优化,都说明了使用Java基础类库的优势,它们往往能够得到最大程度、最高质量的优化,而且只要升级JDK版本,就能零成本地享受这些益处。


关于今天我们讨论的题目你做到心中有数了吗?限于篇幅有限,还有很多字符相关的问题没有来得及讨论,比如编码相关的问题。以后有机会的话Emma会再跟大家探讨。


码了这么多,最后依然是Emma的福利环节~


本期福利:头条最新资料包,内含【最新面试题+笔试题+面经】


领取方式:

关注【爱码士Emma】头条号,回复口令「头条」即可领资料包

更多技术干货、真题和面试经验分享尽在【爱码士Emma】

我们下期再见~


分享到:


相關文章: