一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第4天,点击查看活动详情。
存储结构
jdk8及以前版本String字符串存储结构如下所示
private final char value[];
jdk9开始String字符串存储结构如下所示
private final byte[] value;
private final byte coder;
存储结构由char改为byte的原因: 由于堆中字符串占了很大一部分,并且大部分字符串都是拉丁文,而每个char占用俩字节,拉丁文每个字符使用一个字节就可以存储,所以用char存储字符串就有将近一般的资源都被浪费。为了使堆中字符串空间利用率更高,就使用byte(占用1字节)来存储字符串,用coder标识编码标识符,这样既节省了空间又可以表示像中文这种需要占用两个字节的字符。
不可变性
当对字符串进行重新赋值或者replace()方法修改时,并没有修改原有String的值,而是指向新的String对象。 通过字面量的方式(区别于new)声明一个字符串时,此时字符串声明在字符串常量池中。
String b = "b";
字符串常量池
字符串常量池是一个固定大小的Hashtable,如下图所示。
使用-XX:StringTableSize可设置StringTable的长度
(jdk8开始,1009是设置stringTable长度最小值)
如果Stringtable长度过小,并且放进字符串常量池的String非常多就会导致hash冲突严重,从而链表会很长,这时执行字符串常量池相关操作时性能会大幅下降。
内存分配
使用如下两种方式可以将String存储到字符串常量池中
直接使用双引号声明的String对象
使用String提供的intern()方法将字符串存储到常量池中
jdk6及以前,字符串常量池存放到永久代。
jdk7中,方法区还是存在永久代中,但是将字符串常量池的位置调整到了堆中
java8元空间,字符串常量存储在堆中。
String调整到堆中的原因是因为永久代的回收频率比较低,只在FullGC的时候才会被回收,而FullGC只会在老年代或者永久代空间不足时才会触发。而项目中会有大量的字符串被创建,放在永久代,由于永久代的回收频率低,会导致永久代空间不足。如果放到堆里,能够及时回收内存。