java string 内存泄漏_深入JVM-有关String的内存泄漏

什么是内存泄漏?所谓内存泄漏,就是由于疏忽或错误造成程序未能释放已经不再使用的内存的情况,他并不是说物理内存消失了,而是指由于不再使用的对象占据了内存不被释放,而导致可用内存不断减小,最终有可能导致内存溢出。

由于垃圾回收器的出现,与传统的C/C++相比,Java已经把内存泄漏的概率大大降低了,所以不再使用的对象会由系统自动收集,但这并不意味着已经没有内存泄漏的可能。内存泄漏实际上更是一个应用问题,这里以String.substring()方法为例,说明这种内存泄漏的问题。

在JDK 1.6中,java.lang.String主要由3部分组成:代表字符数据的value、偏移量offset和长度count。

这个结构为内存泄漏埋下了伏笔,字符串的实际内容由value、offset和count三者共同决定,而非value一项。试想,如果字符串value数组包含了100个字符,而count长度只有1个字节,那么这个string实际上只有1个字符,却占据了至少100个字节,那剩余的99个就属于泄漏的部分,他们不会被使用,不会被释放,却长期占用内存,直到字符串本身被回收。

不幸的是,这种情况在JDK 1.6中非常容易出现。下面简单解读一下JDK 1.6中String.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);

}

可以看到,在substring()的视线中,最终是使用了String的构造函数,生成了一个新的String。该构造函数的实现如下:

String(int offset, int count, char value[]) {

this.value = value;

this.offset = offset;

this.count = count;

}

该构造函数并非公有构造函数。这点应该万幸,因为正是这个构造函数引起了内存泄漏问题。新生成的String并没有从value中获取自己需要的那部分,而是简单的使用了相同的value引用,只是修改了offset和count,以此来确定新的String对象的值。当原始字符串没有被回收时,这种情况是没有问题的,并且通过公用value,还可以节省一部分内存,但是一旦原始字符串被回收,value中多余的部分就造成了空间浪费。

综上所述,如果使用了String.substring()将一个大字符串切割为小字符串,当大字符串被回收时,小字符串的存在就会引起内存泄漏。

所幸,这个问题已经引起了官方的重视,在JDK 1.7中,对String的实现有了大幅度的调整。在新版本的String中,去掉了offset和count两项,而String的实质性内容仅仅由value决定,而value数组本身也就代表了这个String实际的取值。下面简单的对比String.length()方法来说明这个问题,代码如下:

//JDK 1.7 实现

public int length() {

return value.length;

}

//JDK 1.6 实现

public int length() {

return count;

}

可以看到,在JDK 1.6中,String长度和value无关。基于这种改进的实现,substring()方法的内存泄漏问题也得以解决,如下代码所示,展示了JDK 1.7 中的String.substring()实现。

public String substring(int beginIndex, int endIndex) {

//省略部分无关内容

int subLen = endIndex - beginIndex;

//省略部分无关内容

return ((beginIndex == 0) && (endIndex == value.length)) ? this : new String(value, beginIndex, subLen);

}

public String(char value[], int offset, int 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);

}

从上述代码可以看到,在新版本的substring中,不再复用原String的value,而是将实际需要的部分做了复制,该问题也得到了完全的修复。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值