Java String.substring内存泄露?

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/
时间: 2024-10-07 09:08:08

Java String.substring内存泄露?的相关文章

Java中的内存泄露 和 JVM GC(垃圾回收机制)

一.什么是Java中的内存泄露? 在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点, 首先,这些对象是可达的,即在有向图中,存在通路可以与其相连:其次,这些对象是无用的,即程序以后不会再使用这些对象. 如果对象满足这两个条件,这些对象就可以判定为Java中的内存泄漏,这些对象不会被GC所回收,然而它却占用内存. 在C++中,内存泄漏的范围更大一些.有些对象被分配了内存空间,然后却不可达,由于C++中没有GC,这些内存将永远收不回来. 在Java中,这些不可达的对象都由GC负

Java 程序的内存泄露问题分析

什么是内存泄露? 广义的Memory Leak:应用占用了内存,但是不再使用(包括不能使用)该部分内存 狭义的Memory Leak:应用分配了内存,但是不能再获取该部分内存的引用(对于Java,也不能被GC) 一个具体的例子: 应用创建了一个长时间运行的Thread 该Thread使用ClassLoader(可以是定制的也可以是默认的)加载了一个类 这个类有一个Static域,指向了一大块内存,然后该Thread的ThreadLocal变量保存了这个类的引用. 最后该Thread清理了对所有已

jdk6 substring 内存泄露问题解析

测试环境 eclipse + jdk1.6.0_25 public class SubMain { private String strs = new String(new byte[100000]); String getString() {     return this.strs.substring(0, 2);   } public static void main(String[] args) {     List<String> list = new ArrayList<St

Java中的内存泄露的几种可能

Java内存泄漏引起的原因: 内存泄漏是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成内存空间的浪费称为内存泄漏. 长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄漏,尽管短生命周期对象已经不再需要,但是因为长生命周期持有它的引用而导致不能被回收,这就是Java中内存泄漏的发生场景. 造成内存泄漏的几种情况: 1.静态集合类引起内存泄漏 像HashMap.Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,他们所引用的所有的对

java String分配内存空间备忘

栈内存 堆内存 基础类型,对象引用(堆内存地址) 由new创建的对象和数组, 存取速度快 相对于栈内存较慢 数据大小声明周期必须确定 分配的内存由java虚拟机自动垃圾回收器管理.动态分配内存大小 共享特性 栈中如果有字符串,则直接引用 如果没有,开辟新的空间存入值 每new一次在堆内存中生成一个新的对象. 创建之后值可以改变 String类声明后则不可改变 一.栈内存 基础类型int, short, long, byte, float, double, boolean, char和对象引用 栈

Java中的内存泄露

JAVA 内存泄露详解(原因、例子及解决)

转载请注明出处:http://blog.csdn.net/anxpp/article/details/51325838,谢谢! Java的一个重要特性就是通过垃圾收集器(GC)自动管理内存的回收,而不需要程序员自己来释放内存.理论上Java中所有不会再被利用的对象所占用的内存,都可以被GC回收,但是Java也存在内存泄露,但它的表现与C++不同. JAVA 中的内存管理 要了解Java中的内存泄露,首先就得知道Java中的内存是如何管理的. 在Java程序中,我们通常使用new为对象分配内存,而

JAVA中会存在内存泄露吗

所谓内存泄露就是指一个不再被程序使用的对象或变量一直被占据在内存中.java中有垃圾回收机制,它可以保证一对象不再被引用的时候,即对象编程了孤儿的时候,对象将自动被垃圾回收器从内存中清除掉.由于Java 使用有向图的方式进行垃圾回收管理,可以消除引用循环的问题,例如有两个对象,相互引用,只要它们和根进程不可达的,那么GC也是可以回收它们的,例如下面的代码可以看到这种情况的内存回收: package com.huawei.interview; import java.io.IOException;

Java中内存泄露及垃圾回收机制

3 垃圾回收机制 3.1 什么是垃圾 垃圾,内存中的垃圾,即内存中已无效但又无法自动释放的空间.在Java语言中,没有引用句柄指向的类对象最容易成为垃圾.,产生垃圾的情况有很多,主要有以下3种: (1)       超出对象的引用句柄的作用域时,这个引用句柄引用的对象就变成垃圾. 例: { Person p1 = new Person(); …… } 引用句柄p1的作用域是从定义到“}”处,执行完这对大括号中的所有代码后,产生的Person对象就会变成垃圾,因为引用这个对象的句柄p1已超过其作用