问题描述
在JDK1.6版本下,运行substring方法报内存泄露,已被Java官方标记,1.7版本已经修复,但是因为公司老项目不肯升JDK,还是要把这个错误拉出来遛一遛。
public class TestGC {
private String largeString = new String(new byte[100000]);
String getString() {
return this.largeString.substring(0,2);
}
public static void main(String[] args) {
java.util.ArrayList list = new java.util.ArrayList();
for (int i = 0; i < 1000000; i++) {
TestGC gc = new TestGC();
list.add(gc.getString());
}
}
}
抛出错误:
java.lang.OutOfMemoryError: Java heap space
源码分析
- value 字符数组,存储字符串实际的内容
- offset 该字符串在字符数组value中的起始位置
- count 字符串包含的字符的长
JDK 1.6下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);
}
String对应的构造方法
String(int offset, int count, char value[]) {
this.value = value;
this.offset = offset;
this.count = count;
}
当我们调用substring方法时,并没有创建一个新的长度为2的新字符串,而是在原有字符串的基础上修改了offset和count。
因为新字符串的生命周期长于代码执行期间,当GC将原字符串的内存回收掉之后,因为新字符串仍旧存在,1gb的内存占用仍然存在。最后导致内存溢出。
substring方法生成的新旧字符串共享内容数组避免了方法调用时字符数组复制,使得代码运行时有了更高的速度,但在特殊情况下就导致了 OOM异常。
解决方法
String littleString = new String(largeString.substring(0,2));
新字符串littleString不再和原字符串共享内容,而调用substring的新旧字符串都会被GC回收掉,就不会产生上面的问题。
Java7的解决方式
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);
}
是不是内存泄漏
顺便讲一下Java内存泄漏到底是什么bug。
内存泄漏是指对象的生命周期已经结束了,应该被GC回收的时候无法被回收掉的bug。
那这个substring的bug产生原因显然不是因为生命周期结束了无法回收,而是因为对象还处在被引用的状态,GC无法回收。如果整个方法能够顺利执行完毕,新旧字符串的引用都会被回收掉。
所以严格意义上,这个应该不算内存泄漏bug。
附上版权声明:
原作者:Vi_error,博客地址:Vi_error.nextval
转载请保持署名和注明原地址