文章目錄
1.首先我們要了解String對象放入常量池的時機
String只會在一種情況下放入常量池,那就是用""修飾時。
例如
String str1 = "abc";
這個時候會把字符串"abc"放入常量池。
又或者當編譯器可以確定變量的值時,如下:
String str1 = "abc" + "def";
這個時候會把字符串"abcdef"放入常量池。
所以我們得出一個結論substring返回值是不會保存在常量池中的,所以他會隨着GC調用而回收
驗證
public static void main(String[] args) throws InterruptedException {
String a = "123456";
String b = a.substring(3);
System.out.println(System.identityHashCode(b.intern()));
b = null;
System.gc();
b = a.substring(3);
System.out.println(System.identityHashCode(b.intern()));
b = null;
System.gc();
b = a.substring(3);
System.out.println(System.identityHashCode(b.intern()));
// 輸出結果
// 1804094807
// 951007336
// 2001049719
}
2.substring返回時究竟發生了什麼操作
JDK1.6中的substring
JDK1.6中String對象由以下3個成員變量組成
String(int offset, int count, char value[]) {
this.value = value;
this.offset = offset;
this.count = count;
}
- value char數組
- offset 偏移量
- count 長度
所以在JDK1.6中substring本質上是生成一個新的String對象,但是引用的char數組是和父對象是同一個數組,只是改變了偏移量與長度
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > count) {
throw new StringIndexOutOfBoundsException(endIndex);
}
if (beginIndex > endIndex) {
throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
}
return ((beginIndex == 0) && (endIndex == count)) ? this :
new String(offset + beginIndex, endIndex - beginIndex, value);
}
JDK1.6中的substring中產生的問題
既然沒有數組還是同一個數組,那麼在回收大字符串的時候會因爲剪切過的字符串還在引用,而導致無法回收,從而引起內存泄漏。這個BUG也被官方收錄並在JDK1.7中得到了修正。
JDK1.7中的substring
首先JDK1.7之後,String取消了offset與count屬性。只保留了char[]。
我們來看一下JDK1.7中的substring源碼
public String substring(int beginIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
int subLen = value.length - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}
public String(char value[], int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count <= 0) {
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
if (offset <= value.length) {
this.value = "".value;
return;
}
}
// Note: offset or count might be near -1>>>1.
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
// 關鍵修改點
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
在計算出偏移量之後不在引用父本的char[]數組,而是調用Arrays.copyOfRange方法生成新的數組。從而生成的新對象與原字符串不再存在聯繫,可以使的原對象得以被回收從而解決了內存泄漏問題。