此問題在項目中被發現,經查看JDK源碼(JDK1.6),String類的public String substring(int beginIndex, int endIndex)的實現讓我很意外。
想重現這個場景很容易,請看代碼。
1
import java.util.ArrayList;
2
import java.util.List;
3
4
public class LeakTest
{
5
public static void main(String
args)
{
6
List<String> handler = new ArrayList<String>();
7
for(int i = 0; i < 100000; i++)
{
8
Huge h = new Huge();
9
handler.add(h.getSubString(1, 5));
10
}
11
}
12
}
13
14
class Huge
{
15
private String str = new String(new char[100000]);
16
public String getSubString(int begin, int end)
{
17
return str.substring(begin, end);
18
}
19
}
執行此代碼結果:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
問題就出在Huge類的 getSubString 方法,它調用了String類的substring方法。
來讓我們看看 substring 類的實現吧,JDK源碼如下:
1
public String substring(int beginIndex, int endIndex)
{
2
if (beginIndex < 0)
{
3
throw new StringIndexOutOfBoundsException(beginIndex);
4
}
5
if (endIndex > count)
{
6
throw new StringIndexOutOfBoundsException(endIndex);
7
}
8
if (beginIndex > endIndex)
{
9
throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
10
}
11
return ((beginIndex == 0) && (endIndex == count)) ? this :
12
new String(offset + beginIndex, endIndex - beginIndex, value);
13
}
再讓我們接下來看看 new String(offset + beginIndex, endIndex - beginIndex, value); 的實現:
1
// Package private constructor which shares value array for speed.
2
String(int offset, int count, char value[])
{
3
this.value = value;
4
this.offset = offset;
5
this.count = count;
6
}
char[] value 數組被共享了。
在我們的main函數里的循環中,每循環一次后,我們希望Huge對象被回收,且釋放它占有的內存。
但實際上 private String str = new String(new char[100000]); 占有的內存并不會被釋放。
因為 我們通過 Huge 類的 getSubString 方法得到的 String 對象還存在(存在于handler的列表中),
它雖然是 length 只有 4 的對象,卻享有著 char[100000] 的空間。
解決方案:
可以修改Huge 類的 getSubString 方法如下:
1
public String getSubString(int begin, int end)
{
2
return new String(str.substring(begin, end));
3
}
只要再套一個String的構造方法即可。
至于為什么,看看JDK源碼,一看便知了。這里就不貼出來了。
唉,以后寫代碼得多多小心啊。
----2010年08月27日
本文為原創,歡迎轉載,轉載請注明出處BlogJava。