码迷,mamicode.com
首页 > 编程语言 > 详细

Java String.substring内存泄露?

时间:2015-08-26 14:09:32      阅读:169      评论:0      收藏:0      [点我收藏+]

标签:

String可以说是最常用的Java类型之一了,但是最近听说JDK6里面String.substring存在内存泄露的bug,伙惊呆!一起来看看到底是啥情况吧。

这个是可以导致Exception in thread "main" java.lang.OutOfMemoryError: Java heap space 的代码: 

public class TestGC {
    private String largeString = new String(new byte[100000]);
 
    String getString() {        return this.largeString.substring(0, 2);//在JDK6里会导致out of memory,在JDK7和8不会出现问题//        return new String("ab");//        return this.largeString.substring(0,2) + "";//JDK6下的解决方法,不会出现out of memory//        return new String(this.largeString.substring(0, 2));/JDK6下的解决方法,不会出现out of memory
    } 
    public static void main(String[] args) {
        java.util.List<String> list = new java.util.ArrayList<String>();        for (int i = 0; i < 100000; i++) {
            TestGC gc = new TestGC();
            list.add(gc.getString());
        }
        System.out.println("over" + list.size());
 
    }
}

 

  但是用JDK8运行,平安无事。注意,之前看的网上文章又说安装了JDK8,只需要在Eclipse里面选Compiler选项为JDK6就可以了,我实 验是不可以的,自己想想String是JDK里面rt.jar的类,就算是编译为JDK6的代码,运行的时候还是用的JDK8的String啊,所以无法 复现bug才是正常的。要复现,只能下载安装JDK6.

有人认为这个会out of memory是因为TestGC对象里面有很大largeString的对象,但是其实在调用getString方法后,TestGC对象完全可以被回收 的,largeString也可以回收,JVM的自动垃圾回收应该不会有bug吧,不然还得了!将getString方法改为直接返回一个String对 象,就可以看出,不会有问题。

现在来看看为什么JDK6里面,substring会导致错误呢。Ctrl+B(IDEA的查看源码快捷键点进去看下),代码如下 

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);
    }

 前面几行主要是做范围检查,最主要的是  

new String(offset + beginIndex, endIndex - beginIndex, value);

 

String(int offset, int count, char value[]) {this.value = value;this.offset = offset;this.count = count;
}

 可以看到JDK6里的substring复用了原来大String的整个value,即String里存放实际char的数组 

/** The value is used for character storage. */
    private final char value[];

 而只是通过修改beginIndex和offset来达到复用value,避免数组copy的麻烦(以及可以提高一点性能),但是问题就是,如果原 String很大,而substring保留的时间比较久,就有可能导致整个很大的value无法回收。JDK6下的修复方法就是,强制生成一个新的 String,避免复用原来String里的value,比如: 

return this.largeString.substring(0,2) + "";//JDK6下的解决方法,不会出现out of memory

 其实,这恰恰也是JDK8里面的实现方式。上src:  

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);
    }

 可以看到,最后对value做了数组copy。

其实JDK8的修改也是褒贬不一,也有人认为JDK6里面的实现方法更好,效率更高,只要自己注意就可以避免问题的,这就是仁者见仁智者见智的问题了,只是需要知道,JDK6里String的这个小坑就好。

参考文章

  1. http://droidyue.com/blog/2014/12/14/substring-memory-issue-in-java/

  2. http://www.programcreek.com/2013/09/the-substring-method-in-jdk-6-and-jdk-7/


Java String.substring内存泄露?

标签:

原文地址:http://my.oschina.net/magicly007/blog/497227

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!