前言
String 是咱们应用最频繁的对象,使用不当会对内存、程序的性能造成影响,本篇文章全面介绍一下 Java 的 String 是如何演进的,以及应用 String 的注意事项。
上面的输入后果是什么?
@Test
public void testString() {
String str1 = "abc";
String str2 = new String("abc");
String str3 = str2.intern();
System.out.println(str1 == str2);
System.out.println(str2 == str3);
System.out.println(str1 == str3);
}
这段代码波及了 Java 字符串的内存调配、新建对象和援用等方面的常识,输入后果是:
false
false
true
String 对象的实现形式
String 对象的实现形式,在 Java 6、Java 7/8、Java 9 中都有很大的区别。上面是一张简要的比照图:
Java 6 的实现形式
String 对 char 数组进行了封装,次要有四个成员变量:
- char 数组
- 偏移量 offset
- 字符数量 count
- 哈希值 hash
String 对象能够通过 offset 和 count 在 char[] 数组中获取对应的字符串,这样做能够高效、疾速地共享数组对象,节俭内存空间,然而这种办法常常导致 内存透露
。
这是因为,如果有一个十分大的字符串数组对象 a,起初有一个小的字符串援用仅援用其中很少的字符 b,那么会新建大的数组 char[],当 a 被开释后,char[] 的援用并不能被 GC,因为 b 还在援用。
Java 7/8 的实现形式
String 类去掉了 offset 和 count,String.substring 办法也不再共享 char[],从而解决了内存透露问题。
Java 9 的实现形式
char[] → byte[]
,同时新增了 coder
属性,标识字符编码。这是因为 char 字符占 16 位(2 个字节),如果仅存储单字节编码的字符就十分节约空间。
coder 属性的作用是标识字符串是否为 Latin-1(单字节编码),0 标识是 Latin-1,1 代表是 UTF-16。
Java 11 中的 java.lang.String#substring(int, int) 办法如下:
public String substring(int beginIndex, int endIndex) {int length = length();
checkBoundsBeginEnd(beginIndex, endIndex, length);
int subLen = endIndex - beginIndex;
if (beginIndex == 0 && endIndex == length) {return this;}
return isLatin1() ? StringLatin1.newString(value, beginIndex, subLen)
: StringUTF16.newString(value, beginIndex, subLen);
}
String 在 JVM 中是如何存储的?
这是一个很重要的问题,置信大部分人都不能形容分明,因为 JVM 的实现改了很多版……
在 JDK 1.7 之前,运行时常量池逻辑蕴含 字符串常量池
,都存在办法区中,办法区在 HotSpot 虚拟机的实现为 永恒代
。
在 JDK 1.7 中,字符串常量池
→ 堆,运行时常量池依然在办法区中。
在 JDK 1.8 中,HotSpot 移除了永恒代,应用元空间(Metaspace)代替。这时候 字符串常量池
在堆中,运行时常量池在元空间(Metaspace)。
永恒代 VS 元空间(Metaspace)
元空间的实质和永恒代相似,都是对 JVM 标准中 办法区的实现
。不过元空间与永恒代之间最大的区别在于:元空间并不在虚拟机中,而是应用 本地内存
。
一句话总结
在新版 JDK 实现(毕竟 Java 8 都曾经是老古董,Java 15 都公布了)中,字符串常量池是在堆中。
应用 String.intern 节俭内存
尽管我还没有在我的项目中理论利用过,不过这个函数应该还挺有用的,可能复用 Java 中的字符串常量。文章结尾的代码中,System.out.println(str1 == str3);
返回 true,就是因为 java.lang.String#intern
办法检测到字符串常量池有这个对象时,可能间接复用字符串常量池的对象,不会额定创立字符串常量。
String str1 = "abc";
String str2 = new String("abc");
留神下面的代码中,new String("abc")
外面的字符串 abc
与 str1 的 abc
不同,是在字符串常量池新创建的 abc
。
String.intern 的代码正文如下。
/**
* Returns a canonical representation for the string object.
* <p>
* A pool of strings, initially empty, is maintained privately by the
* class {@code String}.
* <p>
* When the intern method is invoked, if the pool already contains a
* string equal to this {@code String} object as determined by
* the {@link #equals(Object)} method, then the string from the pool is
* returned. Otherwise, this {@code String} object is added to the
* pool and a reference to this {@code String} object is returned.
* <p>
* It follows that for any two strings {@code s} and {@code t},
* {@code s.intern() == t.intern()} is {@code true}
* if and only if {@code s.equals(t)} is {@code true}.
* <p>
* All literal strings and string-valued constant expressions are
* interned. String literals are defined in section 3.10.5 of the
* <cite>The Java™ Language Specification</cite>.
*
* @return a string that has the same contents as this string, but is
* guaranteed to be from a pool of unique strings.
* @jls 3.10.5 String Literals
*/
public native String intern();
公众号
coding 笔记、点滴记录,当前的文章也会同步到公众号(Coding Insight)中,心愿大家关注 ^_^
代码和思维导图在 GitHub 我的项目中,欢送大家 star!