Java中由substring方法引发的内存泄漏详解
内存溢出(out of memory ) :通俗的说就是内存不够用了,比如在一个无限循环中不断创建一个大的对象,很快就会引发内存溢出。
内存泄漏(leak of memory) :是指为一个对象分配内存之后,在对象已经不在使用时未及时的释放,导致一直占据内存单元,使实际可用内存减少,就好像内存泄漏了一样。
由substring方法引发的内存泄漏
substring(int beginindex, int endndex )是string类的一个方法,但是这个方法在jdk6和jdk7中的实现是完全不同的(虽然它们都达到了同样的效果)。了解它们实现细节上的差异,能够更好的帮助你使用它们,因为在jdk1.6中不当使用substring会导致严重的内存泄漏问题。
1、substring的作用
substring(int beginindex, int endindex)方法返回一个子字符串,从父字符串的beginindex开始,结束于endindex-1。父字符串的下标从0开始,子字符串包含beginindex而不包含endindex
string x= "abcdef"; x= str.substring(1,3); system.out.println(x);
上述程序的输出是“bc”
2、实现原理
string类是不可变变,当上述第二句中x被重新赋值的时候,它会指向一个新的字符串对象。然而,没有准确说明的或者代表堆中发生的实际情况,当substring被调用的时候真正发生的才是这两者的差别。
jdk6中的substring实现
string对象被当作一个char数组来存储,在string类中有3个域:char[] value、int offset、int count,分别用来存储真实的字符数组,数组的起始位置,string的字符数。由这3个变量就可以决定一个字符串。当substring方法被调用的时候,它会创建一个新的字符串,但是上述的char数组value仍然会使用原来父数组的那个value。父数组和子数组的唯一差别就是count和offset的值不一样。
看一下jdk6中substring的实现源码:
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); //使用的是和父字符串同一个char数组value }
string(int offset, int count, char value[]) { this.value = value; this.offset = offset; this.count = count; }
string str = "abcdefghijklmnopqrst"; string sub = str.substring(1, 3); str = null;
这段简单的程序有两个字符串变量str、sub。sub字符串是由父字符串str截取得到的,假如上述这段程序在jdk1.6中运行,我们知道数组的内存空间分配是在堆上进行的,那么sub和str的内部char数组value是公用了同一个,也就是上述有字符a~字符t组成的char数组,str和sub唯一的差别就是在数组中其实beginindex和字符长度count的不同。在第三句,我们使str引用为空,本意是释放str占用的空间,但是这个时候,gc是无法回收这个大的char数组的,因为还在被sub字符串内部引用着,虽然sub只截取这个大数组的一小部分。当str是一个非常大字符串的时候,这种浪费是非常明显的,甚至会带来性能问题,解决这个问题可以是通过以下的方法:
利用的就是字符串的拼接技术,它会创建一个新的字符串,这个新的字符串会使用一个新的内部char数组存储自己实际需要的字符,这样父数组的char数组就不会被其他引用,令str=null,在下一次gc回收的时候会回收整个str占用的空间。但是这样书写很明显是不好看的,所以在jdk7中,substring 被重新实现了。
jdk7中的substring实现
在jdk7中改进了substring的实现,它实际是为截取的子字符串在堆中创建了一个新的char数组用于保存子字符串的字符。
查看jdk7中string类的substring方法的实现源码:
public string substring(int beginindex, int endindex) { if (beginindex < 0) { throw new stringindexoutofboundsexception(beginindex); } if (endindex > value.length) { throw new stringindexoutofboundsexception(endindex); } int sublen = endindex - beginindex; if (sublen < 0) { throw new stringindexoutofboundsexception(sublen); } return ((beginindex == 0) && (endindex == value.length)) ? this : new string(value, beginindex, sublen); }
public string(char value[], int offset, int count) { if (offset < 0) { throw new stringindexoutofboundsexception(offset); } if (count < 0) { throw new stringindexoutofboundsexception(count); } // 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); }
arrays类的copyofrange方法:
public static char[] copyofrange(char[] original, int from, int to) { int newlength = to - from; if (newlength < 0) throw new illegalargumentexception(from + " > " + to); char[] copy = new char[newlength]; //是创建了一个新的char数组 system.arraycopy(original, from, copy, 0, math.min(original.length - from, newlength)); return copy; }
可以发现是去为子字符串创建了一个新的char数组去存储子字符串中的字符。这样子字符串和父字符串也就没有什么必然的联系了,当父字符串的引用失效的时候,gc就会适时的回收父字符串占用的内存空间。
总结
以上就是本文关于java中由substring方法引发的内存泄漏详解的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站其他相关专题,如有不足之处,欢迎留言指出。感谢朋友们对本站的支持!