Java中由substring方法引发的内存泄漏

来源:互联网 发布:剑桥国际英语 知乎 编辑:程序博客网 时间:2024/05/20 09:26

在Java中我们无须关心内存的释放,JVM提供了内存管理机制,有垃圾回收器帮助回收不需要的对象。但实际中一些不当的使用仍然会导致一系列的内存问题,常见的就是内存泄漏和内存溢出

内存溢出(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= <span class="string" style="color: rgb(221, 17, 68);">"abcdef"</span>;x= str.substring(<span class="number" style="color: rgb(0, 153, 153);">1</span>,<span class="number" style="color: rgb(0, 153, 153);">3</span>);System.<span class="keyword" style="font-weight: bold;">out</span>.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的实现源码:

<span class="keyword" style="font-weight: bold;">public</span> String substring(<span class="keyword" style="font-weight: bold;">int</span> beginIndex, <span class="keyword" style="font-weight: bold;">int</span> endIndex) {<span class="indent">  </span><span class="keyword" style="font-weight: bold;">if</span> (beginIndex < <span class="number" style="color: rgb(0, 153, 153);">0</span>) {<span class="indent">  </span>    <span class="keyword" style="font-weight: bold;">throw</span> <span class="keyword" style="font-weight: bold;">new</span> StringIndexOutOfBoundsException(beginIndex);<span class="indent">  </span>}<span class="indent">  </span><span class="keyword" style="font-weight: bold;">if</span> (endIndex > count) {<span class="indent">  </span>    <span class="keyword" style="font-weight: bold;">throw</span> <span class="keyword" style="font-weight: bold;">new</span> StringIndexOutOfBoundsException(endIndex);<span class="indent">  </span>}<span class="indent">  </span><span class="keyword" style="font-weight: bold;">if</span> (beginIndex > endIndex) {<span class="indent">  </span>    <span class="keyword" style="font-weight: bold;">throw</span> <span class="keyword" style="font-weight: bold;">new</span> StringIndexOutOfBoundsException(endIndex - beginIndex);<span class="indent">  </span>}<span class="indent">  </span><span class="keyword" style="font-weight: bold;">return</span> ((beginIndex == <span class="number" style="color: rgb(0, 153, 153);">0</span>) && (endIndex == count)) ? <span class="keyword" style="font-weight: bold;">this</span> :<span class="indent">  </span>    <span class="keyword" style="font-weight: bold;">new</span> String(offset + beginIndex, endIndex - beginIndex, <span class="keyword" style="font-weight: bold;">value</span>); <span class="comment" style="color: rgb(153, 153, 136); font-style: italic;">//使用的是和父字符串同一个char数组value</span>    }
String(int off<span class="operator"><span class="keyword" style="font-weight: bold;">set</span>, <span class="keyword" style="font-weight: bold;">int</span> <span class="aggregate">count</span>, <span class="keyword" style="font-weight: bold;">char</span> <span class="keyword" style="font-weight: bold;">value</span>[]) {<span class="indent">  </span>this.<span class="keyword" style="font-weight: bold;">value</span> = <span class="keyword" style="font-weight: bold;">value</span>;</span><span class="indent">  </span>this.off<span class="operator"><span class="keyword" style="font-weight: bold;">set</span> = offset;</span><span class="indent">  </span>this.count = count;    }

由此引发的内存泄漏泄漏情况:

String str = <span class="string" style="color: rgb(221, 17, 68);">"abcdefghijklmnopqrst"</span>;String <span class="sub"><span class="keyword" style="font-weight: bold;">sub</span> = str.substring(1, 3);</span>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是一个非常大字符串的时候,这种浪费是非常明显的,甚至会带来性能问题,解决这个问题可以是通过以下的方法:
String str = <span class="string" style="color: rgb(221, 17, 68);">"abcdefghijklmnopqrst"</span>;String <span class="sub"><span class="keyword" style="font-weight: bold;">sub</span> = str.substring(1, 3) + "";</span>str = null;
利用的就是字符串的拼接技术,它会创建一个新的字符串,这个新的字符串会使用一个新的内部char数组存储自己实际需要的字符,这样父数组的char数组就不会被其他引用,令str=null,在下一次GC回收的时候会回收整个str占用的空间。但是这样书写很明显是不好看的,所以在JDK7中,substring 被重新实现了。

JDK7中的substring实现

在JDK7中改进了substring的实现,它实际是为截取的子字符串在堆中创建了一个新的char数组用于保存子字符串的字符。下面的这张图说明了JDK7中substring的实现过程:

查看JDK7中String类的substring方法的实现源码:

<span class="keyword" style="font-weight: bold;">public</span> String substring(<span class="keyword" style="font-weight: bold;">int</span> beginIndex, <span class="keyword" style="font-weight: bold;">int</span> endIndex) {<span class="indent">  </span><span class="indent">  </span><span class="keyword" style="font-weight: bold;">if</span> (beginIndex < <span class="number" style="color: rgb(0, 153, 153);">0</span>) {<span class="indent">  </span><span class="indent">  </span><span class="indent">  </span><span class="keyword" style="font-weight: bold;">throw</span> <span class="keyword" style="font-weight: bold;">new</span> StringIndexOutOfBoundsException(beginIndex);<span class="indent">  </span><span class="indent">  </span>}<span class="indent">  </span><span class="indent">  </span><span class="keyword" style="font-weight: bold;">if</span> (endIndex > <span class="keyword" style="font-weight: bold;">value</span>.length) {<span class="indent">  </span><span class="indent">  </span><span class="indent">  </span><span class="keyword" style="font-weight: bold;">throw</span> <span class="keyword" style="font-weight: bold;">new</span> StringIndexOutOfBoundsException(endIndex);<span class="indent">  </span><span class="indent">  </span>}<span class="indent">  </span><span class="indent">  </span><span class="keyword" style="font-weight: bold;">int</span> subLen = endIndex - beginIndex;<span class="indent">  </span><span class="indent">  </span><span class="keyword" style="font-weight: bold;">if</span> (subLen < <span class="number" style="color: rgb(0, 153, 153);">0</span>) {<span class="indent">  </span><span class="indent">  </span><span class="indent">  </span><span class="keyword" style="font-weight: bold;">throw</span> <span class="keyword" style="font-weight: bold;">new</span> StringIndexOutOfBoundsException(subLen);<span class="indent">  </span><span class="indent">  </span>}<span class="indent">  </span><span class="indent">  </span><span class="keyword" style="font-weight: bold;">return</span> ((beginIndex == <span class="number" style="color: rgb(0, 153, 153);">0</span>) && (endIndex == <span class="keyword" style="font-weight: bold;">value</span>.length)) ? <span class="keyword" style="font-weight: bold;">this</span><span class="indent">  </span><span class="indent">  </span><span class="indent">  </span><span class="indent">  </span>: <span class="keyword" style="font-weight: bold;">new</span> String(<span class="keyword" style="font-weight: bold;">value</span>, beginIndex, subLen);<span class="indent">  </span>}
<span class="keyword" style="font-weight: bold;">public</span> String(<span class="keyword" style="font-weight: bold;">char</span> <span class="keyword" style="font-weight: bold;">value</span>[], <span class="keyword" style="font-weight: bold;">int</span> offset, <span class="keyword" style="font-weight: bold;">int</span> count) {<span class="indent">  </span><span class="indent">  </span><span class="keyword" style="font-weight: bold;">if</span> (offset < <span class="number" style="color: rgb(0, 153, 153);">0</span>) {<span class="indent">  </span><span class="indent">  </span><span class="indent">  </span><span class="keyword" style="font-weight: bold;">throw</span> <span class="keyword" style="font-weight: bold;">new</span> StringIndexOutOfBoundsException(offset);<span class="indent">  </span><span class="indent">  </span>}<span class="indent">  </span><span class="indent">  </span><span class="keyword" style="font-weight: bold;">if</span> (count < <span class="number" style="color: rgb(0, 153, 153);">0</span>) {<span class="indent">  </span><span class="indent">  </span><span class="indent">  </span><span class="keyword" style="font-weight: bold;">throw</span> <span class="keyword" style="font-weight: bold;">new</span> StringIndexOutOfBoundsException(count);<span class="indent">  </span><span class="indent">  </span>}<span class="indent">  </span><span class="indent">  </span><span class="comment" style="color: rgb(153, 153, 136); font-style: italic;">// Note: offset or count might be near -1>>>1.</span><span class="indent">  </span><span class="indent">  </span><span class="keyword" style="font-weight: bold;">if</span> (offset > <span class="keyword" style="font-weight: bold;">value</span>.length - count) {<span class="indent">  </span><span class="indent">  </span><span class="indent">  </span><span class="keyword" style="font-weight: bold;">throw</span> <span class="keyword" style="font-weight: bold;">new</span> StringIndexOutOfBoundsException(offset + count);<span class="indent">  </span><span class="indent">  </span>}<span class="indent">  </span><span class="indent">  </span><span class="keyword" style="font-weight: bold;">this</span>.<span class="keyword" style="font-weight: bold;">value</span> = Arrays.copyOfRange(<span class="keyword" style="font-weight: bold;">value</span>, offset, offset+count);<span class="indent">  </span>}
Arrays类的copyOfRange方法:
<span class="keyword" style="font-weight: bold;">public</span> <span class="keyword" style="font-weight: bold;">static</span> <span class="keyword" style="font-weight: bold;">char</span>[] copyOfRange(<span class="keyword" style="font-weight: bold;">char</span>[] original, <span class="keyword" style="font-weight: bold;">int</span> <span class="keyword" style="font-weight: bold;">from</span>, <span class="keyword" style="font-weight: bold;">int</span> to) {        <span class="keyword" style="font-weight: bold;">int</span> newLength = to - <span class="keyword" style="font-weight: bold;">from</span>;        <span class="keyword" style="font-weight: bold;">if</span> (newLength < <span class="number" style="color: rgb(0, 153, 153);">0</span>)            <span class="keyword" style="font-weight: bold;">throw</span> <span class="keyword" style="font-weight: bold;">new</span> IllegalArgumentException(<span class="keyword" style="font-weight: bold;">from</span> + <span class="string" style="color: rgb(221, 17, 68);">" > "</span> + to);        <span class="keyword" style="font-weight: bold;">char</span>[] copy = <span class="keyword" style="font-weight: bold;">new</span> <span class="keyword" style="font-weight: bold;">char</span>[newLength];   <span class="comment" style="color: rgb(153, 153, 136); font-style: italic;">//是创建了一个新的char数组</span>        System.arraycopy(original, <span class="keyword" style="font-weight: bold;">from</span>, copy, <span class="number" style="color: rgb(0, 153, 153);">0</span>,                         Math.min(original.length - <span class="keyword" style="font-weight: bold;">from</span>, newLength));        <span class="keyword" style="font-weight: bold;">return</span> copy;    }
可以发现是去为子字符串创建了一个新的char数组去存储子字符串中的字符。这样子字符串和父字符串也就没有什么必然的联系了,当父字符串的引用失效的时候,GC就会适时的回收父字符串占用的内存空间。
0 0
原创粉丝点击