substring内存泄露原因及解决

问题描述

在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
转载请保持署名和注明原地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值