Java中substring内存泄露问题
2017-08-28 22:35
246 查看
在Java中,String是最常用的数据类型,String有一个substring方法用来截取字符串,或许我们没注意到该方法可能会引起内存泄露问题(
运行上面代码,如果使用Java6(Java7以上不会抛异常)运行一下就会报如下异常,说明没有足够的堆内存供我们创建对象
于是有人会说,我们每个循环都创建一个Test对象,100万条数据存储到ArrayList中,这样必然会造成OOM,其实不然,看下面这段代码,只修改getString()方法
执行上面的方法,并不会导致OOM异常,因为我们持有的时1000000个ab字符串对象,而Test对象(包括其中的largeString)会在java的垃圾回收中释放掉。所以这里不会存在内存溢出。
调用的 String 构造函数源码为:
我们发现 String.substring()所返回的 String 仍然会保存原始 String,其实substring中生成的字符串与原字符串共享内容数组是一个很棒的设计,这样避免了每次进行substring重新进行字符数组复制。这种设计在很多时候可以很大程度的节省内存,因为这些 String 都复用了原始 String,只是通过 int 类型的 start, end 等值来标识每一个 String。而对于上面的案例,从一个巨大的 String 截取少数 String 为以后所用,这样的设计则造成大量冗余数据。
既然导致大量内存占用的根源是 String.substring()返回结果中包含大量原始 String,那么一个显而易见的减少内存浪费的的途径就是去除这些原始 String。办法有很多种,在此我们采取比较直观的一种,即再次调用 new String构造一个的仅包含截取出的字符串的 String
substring方法中调用的构造方法,进行内容字符数组复制。
出现于Java6中)。
方法介绍:
在Java中提供了两个截取子字符串的方法:
substring(int beginIndex) substring(int beginIndex, int endIndex)
问题重现:
public class Test { private String largeString = new String(new byte[100000]); String getString() { return this.largeString.substring(0, 2); } public static void main(String[] args) { List<String> list = new ArrayList<String>(); for (int i = 0; i < 1000000; i++) { Test t = new Test(); list.add(t.getString()); } } }
运行上面代码,如果使用Java6(Java7以上不会抛异常)运行一下就会报如下异常,说明没有足够的堆内存供我们创建对象
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.lang.StringCoding$StringDecoder.decode(StringCoding.java:133) at java.lang.StringCoding.decode(StringCoding.java:173) at java.lang.StringCoding.decode(StringCoding.java:185)
于是有人会说,我们每个循环都创建一个Test对象,100万条数据存储到ArrayList中,这样必然会造成OOM,其实不然,看下面这段代码,只修改getString()方法
public class Test { private String largeString = new String(new byte[100000]); String getString() { //return this.largeString.substring(0, 2); return new String("ab"); } public static void main(String[] args) { List<String> list = new ArrayList<String>(); for (int i = 0; i < 1000000; i++) { Test t = new Test(); list.add(t.getString()); } } }
执行上面的方法,并不会导致OOM异常,因为我们持有的时1000000个ab字符串对象,而Test对象(包括其中的largeString)会在java的垃圾回收中释放掉。所以这里不会存在内存溢出。
那么究竟是什么导致的内存泄露呢?
在 JDK 1.6 中 String.substring(int, int)的源码为:
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; }
我们发现 String.substring()所返回的 String 仍然会保存原始 String,其实substring中生成的字符串与原字符串共享内容数组是一个很棒的设计,这样避免了每次进行substring重新进行字符数组复制。这种设计在很多时候可以很大程度的节省内存,因为这些 String 都复用了原始 String,只是通过 int 类型的 start, end 等值来标识每一个 String。而对于上面的案例,从一个巨大的 String 截取少数 String 为以后所用,这样的设计则造成大量冗余数据。
既然导致大量内存占用的根源是 String.substring()返回结果中包含大量原始 String,那么一个显而易见的减少内存浪费的的途径就是去除这些原始 String。办法有很多种,在此我们采取比较直观的一种,即再次调用 new String构造一个的仅包含截取出的字符串的 String
String newString = new String(largeString.substring(0,2));
Java 7 实现
在Java 7 中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); }
substring方法中调用的构造方法,进行内容字符数组复制。
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中substring内存泄露问题
- Java内存泄露问题分析
- Java垃圾回收机制与内存泄露问题
- JAVA垃圾回收机制与内存泄露问题
- Java内存泄露问题
- JAVA垃圾回收机制与内存泄露问题
- Java内存泄露问题
- Java线程引起的内存泄露问题浅析
- Java内存泄露问题
- Java内存泄露问题
- Java的内存泄露问题
- Linux下用JMap对Java程序进行性能测试检查内存泄露问题
- Linux下用JMap对Java程序进行性能测试检查内存泄露问题
- JAVA程序中也可能会发生内存泄露的问题
- Java内存泄露问题分析
- 关于Java内存泄露的问题
- Java内存泄露问题分析
- Java内存泄露问题
- 解决Java内存泄露问题方案
- JAVA垃圾回收机制与内存泄露问题