subString() 泄露原因
在java程序性能优化一书中看到自己常用的方法竟然会内存泄露,马上记录下来。
1. 字符串操作中截取字符串是常用的操作,在java中,String类提供了两个截取字符串的方法:
public String subString(int beginIndex);
public String subString(int beginIndex,int endIndex)
用第二个方法来分析造成内存泄露的原因
public String subString(int beginIndex,int endIndex){
if(beginIndex < 0){
throw new StringIndexOutOfBoundException(beginIndex);
}
if(endIndex >count){//String对象可以认为是char数组的延伸和进一步封装,这里的count指字符串长度
throw new StringIndexOutOfBoundException(endIndex);
}
if(beginIndex > endIndex){
throw new StringIdexOutOfBoundException(endIndex-beginIndex);
}
return ((beginIndex==0)&&(endIndex==count))?this: new String(offset+beginIndex,endIndex-beginIndex,value);
}
//被调用的String 的构造对象参数有偏移量,长度和char数组
String (int offset,int count,char value[]){
this.value = value;
this.offset = offset;
this.value = value;
}
在源码注释中说明,这是一个包作用域的构造函数,其目的是为了高效且快速的共享String内的char数组对象,但在这种通过偏移量截取字符串的方法,String的原生内容value被复制到子字符串中,如果,截取的原生字符串很长,但截取内容很短,在截取出来的子字符串中包含原生字符串所有内容,并占有一定的内存空间。这样实际提高了运算速度却浪费了内存空间。
解决subString 内存泄露问题
书中举了一个例子来说明
public static void main(String args[]){
List<String> handler = new ArrayList<String>();
for(int i =0;i<100;i++){
HugeStr h = new HugeStr(); //此方法不到1000次就内存溢出
//ImprovedHugeStr h = new ImprovedHugeStr();
handler.add(h.getSubString(1,5));
}
}
static class HugeStr{
private String str = new String(new char[100000]);
public String getSubString(int begin,int end){
return str.subString(begin,end);
}
}
static class ImproveHugeStr{
private String str = new String(new char[100000]);
public String getSubString(int begin,int end){
return new String(str.subString(begin,end));
}
}
ImproveHugeStr方法能够很好工作的关键是因为它使用了没有内存泄露的String构造函数重新生成了String对象,使得由subString方法返回的存在内存泄露问题的String对象失去所有的强引用,从而被垃圾回收器识别为垃圾对象回收,保证了系统内存的稳定