- 浏览: 465461 次
- 性别:
- 来自: 杭州
文章分类
最新评论
-
ty1972873004:
sunwang810812 写道我运行了这个例子,怎么结果是这 ...
Java并发编程: 使用Semaphore限制资源并发访问的线程数 -
lgh1992314:
simpleDean 写道请问,Logger.setLevel ...
Java内置Logger详解 -
sunwang810812:
我运行了这个例子,怎么结果是这样的:2号车泊车6号车泊车5号车 ...
Java并发编程: 使用Semaphore限制资源并发访问的线程数 -
jp260715007:
nanjiwubing123 写道参考你的用法,用如下方式实现 ...
面试题--三个线程循环打印ABC10次的几种解决方法 -
cb_0312:
SurnameDictionary文章我没看完,现在懂了
中文排序
本文将对String.substring方法可能产生内存泄漏的问题进行分析,并给出相应的优化方法。
String.substring内存泄漏分析
首先看一下JDK6 String.substring的源代码:
从上述的源代码可以看出,使用substring获取子字符串方法中,原有字符串的内容value(char[])将继续重用。这种方式提高了运算速度却要在内存中保留原来字符串的内容。
例如: 读取一个5000个字符的字符串,采用substring截取其中的30个字符,在这种情况下,30个字符在内存中还是使用了5000个字符。
设想一下:如果字符串更大,比如一百万个字符,而substring只需要其中的几十个,这样的情况下将会占有较多的内存空间。如果实例多需要调用的次数多,那么很容易造成内存泄漏。
请看下面的一个例子:
避免substring的优化方法
1. 创建新的字符串。
2. 使用intern()
在测试代码中,采用默认VM参数的,分别调用huge.subString1(0, i), huge.subString2(0, i)和huge.subString3(0, i)运行程序,得到的结果如下:
a)采用huge.subString1(0, i)遇到OutOfMemoryError
b)采用huge.subString2(0, i)和huge.subString3(0, i)的运行正常。
采用intern()方法会有其它的影响,因为我们将使用PermGen Space. 除非VM有足够的空间,否则也会抛出OutOfMemoryError.
比如:
使用参数-XX:PermSize=1M -XX:MaxPermSize=1M
采用huge.subString3(0, i)再运行一下:
在这种情况下,只有采用huge.subString2(0, i)的方式还能正常运行,采用huge.subString1(0, i)和huge.subString3(0, i)方法都产生了OutOfMemoryError。
比较一下打印出来的循环次数,采用intern()方法运行次数比直接采用String.substring的运行次数多很多。
通过上面的例子可以得出如下几个结论:
1. String.substring存在内存泄漏的危险。
2. 采用新建字符串和String.intern()的方法可以优化直接调用String.substring。
首先选择的是新建字符串。其次才是选择通过intern()方法。intern()方法使用有其局限性。这个只有在从大字符串中截取比较小的子字符串,并且原来的字符串不需要再继续使用的场景下有较好的作用。
谢谢你的评论。
确实,如果一直创建对象并放入ArrayList中也会引起堆内存的溢出。
String.substring内存泄漏分析
首先看一下JDK6 String.substring的源代码:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final char value[]; /** * Returns a new string that is a substring of this string. The * substring begins with the character at the specified index and * extends to the end of this string. <p> * Examples: * <blockquote><pre> * "unhappy".substring(2) returns "happy" * "Harbison".substring(3) returns "bison" * "emptiness".substring(9) returns "" (an empty string) * </pre></blockquote> * * @param beginIndex the beginning index, inclusive. * @return the specified substring. * @exception IndexOutOfBoundsException if * <code>beginIndex</code> is negative or larger than the * length of this <code>String</code> object. */ public String substring(int beginIndex) { return substring(beginIndex, count); } /** * Returns a new string that is a substring of this string. The * substring begins at the specified <code>beginIndex</code> and * extends to the character at index <code>endIndex - 1</code>. * Thus the length of the substring is <code>endIndex-beginIndex</code>. * <p> * Examples: * <blockquote><pre> * "hamburger".substring(4, 8) returns "urge" * "smiles".substring(1, 5) returns "mile" * </pre></blockquote> * * @param beginIndex the beginning index, inclusive. * @param endIndex the ending index, exclusive. * @return the specified substring. * @exception IndexOutOfBoundsException if the * <code>beginIndex</code> is negative, or * <code>endIndex</code> is larger than the length of * this <code>String</code> object, or * <code>beginIndex</code> is larger than * <code>endIndex</code>. */ 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获取子字符串方法中,原有字符串的内容value(char[])将继续重用。这种方式提高了运算速度却要在内存中保留原来字符串的内容。
例如: 读取一个5000个字符的字符串,采用substring截取其中的30个字符,在这种情况下,30个字符在内存中还是使用了5000个字符。
设想一下:如果字符串更大,比如一百万个字符,而substring只需要其中的几十个,这样的情况下将会占有较多的内存空间。如果实例多需要调用的次数多,那么很容易造成内存泄漏。
请看下面的一个例子:
package my.memoryLeak; import java.util.ArrayList; import java.util.List; public class MemoryLeakExample { public static void main(String[] args) { /** -XX:PermSize=1M -XX:MaxPermSize=1M */ List<String> substringList = new ArrayList<String>(); /** * 循环3000次。 * 第i次循环截取前i个字符串 */ for (int i = 1; i <= 3000; i++) { HugeString huge = new HugeString(); System.out.println(i); substringList.add(huge.subString1(0, i)); } } } class HugeString { private String str = new String(new char[1000000]); /** * 调用String的subString方法来实现。 * 例如: 读取一个5000个字符的字符串,采用substring截取其中的30个字符,在这种情况下,30个字符在内存中还是使用了5000个字符。 * 设想一下:如果字符串更大,比如一百万个字符,而substring只需要其中的几十个, * 这样的情况下会将会占有较多的内存空间。如果实例多需要调用的次数多,那么很容易造成内存泄漏。 * Exception in thread "main" java.lang.OutOfMemoryError: Java heap space * at java.util.Arrays.copyOf(Unknown Source) * at java.lang.String.<init>(Unknown Source) * at my.memoryLeak.Huge.<init>(LeakTest.java:38) * at my.memoryLeak.MemoryLeakExample.main(MemoryLeakExample.java:13) * */ public String subString1(int begin, int end) { return str.substring(begin, end); } /** * 采用新建的方式,避免在内存中占有较多的内容。 */ public String subString2(int begin, int end) { return new String(str.substring(begin, end)); } /** * 将substring的内容存放到常量池。 * 这种情况下,会用到PermGen space,如果过度使用,可能导致PermGen Sapce用完,跑出异常。 * * 可以使用如下参数调整大小,如 * -XX:PermSize=1M -XX:MaxPermSize=1M * * Exception in thread "main" java.lang.OutOfMemoryError: PermGen space * at java.lang.String.intern(Native Method) * at my.memoryLeak.HugeString.subString3(MemoryLeakExample.java:55) * at my.memoryLeak.MemoryLeakExample.main(MemoryLeakExample.java:15) */ public String subString3(int begin, int end) { return str.substring(begin, end).intern(); } }
避免substring的优化方法
1. 创建新的字符串。
/** * 采用新建的方式,避免在内存中占有较多的内容。 */ public String subString2(int begin, int end) { return new String(str.substring(begin, end)); }
2. 使用intern()
/** * 将substring的内容存放到常量池。 * 这种情况下,会用到PermGen space,如果过度使用,可能导致PermGen Sapce用完,跑出异常。 * * 可以使用如下参数调整大小,如 * -XX:PermSize=1M -XX:MaxPermSize=1M * * Exception in thread "main" java.lang.OutOfMemoryError: PermGen space * at java.lang.String.intern(Native Method) * at my.memoryLeak.HugeString.subString3(MemoryLeakExample.java:55) * at my.memoryLeak.MemoryLeakExample.main(MemoryLeakExample.java:15) */ public String subString3(int begin, int end) { return str.substring(begin, end).intern(); }
在测试代码中,采用默认VM参数的,分别调用huge.subString1(0, i), huge.subString2(0, i)和huge.subString3(0, i)运行程序,得到的结果如下:
a)采用huge.subString1(0, i)遇到OutOfMemoryError
b)采用huge.subString2(0, i)和huge.subString3(0, i)的运行正常。
采用intern()方法会有其它的影响,因为我们将使用PermGen Space. 除非VM有足够的空间,否则也会抛出OutOfMemoryError.
比如:
使用参数-XX:PermSize=1M -XX:MaxPermSize=1M
采用huge.subString3(0, i)再运行一下:
在这种情况下,只有采用huge.subString2(0, i)的方式还能正常运行,采用huge.subString1(0, i)和huge.subString3(0, i)方法都产生了OutOfMemoryError。
比较一下打印出来的循环次数,采用intern()方法运行次数比直接采用String.substring的运行次数多很多。
通过上面的例子可以得出如下几个结论:
1. String.substring存在内存泄漏的危险。
2. 采用新建字符串和String.intern()的方法可以优化直接调用String.substring。
首先选择的是新建字符串。其次才是选择通过intern()方法。intern()方法使用有其局限性。这个只有在从大字符串中截取比较小的子字符串,并且原来的字符串不需要再继续使用的场景下有较好的作用。
评论
4 楼
MouseLearnJava
2013-08-23
frank-liu 写道
楼主所说的这种场景适用于连续访问大的字符串的情况,比如说从网上连续读字符串,每次读出来一大堆的时候取中间一部分。在jdk6及更早的版本里用substring()方法会导致新生成的string实际上内部还是有指向原来数组内部char[]的引用,gc就不能把生成的这些大的字符串回收,这样就导致内存溢出了。
只是举的这个例子有点容易让人误解,因为ArrayList里面添加的元素如果不用remove方法移除的话,后面只要这个ArrayList还在被使用它里面的元素就gc不了。这样如果加入的元素过多,有可能导致把系统内存耗光,这就相当于故意耗内存而不是substring的错了。
只是举的这个例子有点容易让人误解,因为ArrayList里面添加的元素如果不用remove方法移除的话,后面只要这个ArrayList还在被使用它里面的元素就gc不了。这样如果加入的元素过多,有可能导致把系统内存耗光,这就相当于故意耗内存而不是substring的错了。
谢谢你的评论。
确实,如果一直创建对象并放入ArrayList中也会引起堆内存的溢出。
3 楼
frank-liu
2013-08-23
楼主所说的这种场景适用于连续访问大的字符串的情况,比如说从网上连续读字符串,每次读出来一大堆的时候取中间一部分。在jdk6及更早的版本里用substring()方法会导致新生成的string实际上内部还是有指向原来数组内部char[]的引用,gc就不能把生成的这些大的字符串回收,这样就导致内存溢出了。
只是举的这个例子有点容易让人误解,因为ArrayList里面添加的元素如果不用remove方法移除的话,后面只要这个ArrayList还在被使用它里面的元素就gc不了。这样如果加入的元素过多,有可能导致把系统内存耗光,这就相当于故意耗内存而不是substring的错了。
只是举的这个例子有点容易让人误解,因为ArrayList里面添加的元素如果不用remove方法移除的话,后面只要这个ArrayList还在被使用它里面的元素就gc不了。这样如果加入的元素过多,有可能导致把系统内存耗光,这就相当于故意耗内存而不是substring的错了。
2 楼
MouseLearnJava
2013-08-22
JDK1.7后对substring做了改进。JDK1.6及其以下的版本中substring都会共享value。我贴的代码是JDK1.6版本的。
上面的例子只是说明了substring可能产生的问题,以下面的代码为例,
尽管我们使用了huge.substring截取了很小部分的子字符串,却占用了char[1000000]的内存空间。这循环体执行过程中,huge添加到了list中,垃圾回收机制并不回回收huge中的字符串。
随着实例的增多,占用的内存空间也将增多,这就是导致出现问题的原因。
目前,很多系统依然采用1.6及其以下的JDK版本,所以,使用substring时还是需要小心点的。 不过,一般情况下,这种问题很少发生。
上面的例子只是说明了substring可能产生的问题,以下面的代码为例,
/** -XX:PermSize=1M -XX:MaxPermSize=1M */ List<String> substringList = new ArrayList<String>(); /** * 循环3000次。 * 第i次循环截取前i个字符串 */ for (int i = 1; i <= 3000; i++) { HugeString huge = new HugeString(); System.out.println(i); substringList.add(huge.subString1(0, i)); }
尽管我们使用了huge.substring截取了很小部分的子字符串,却占用了char[1000000]的内存空间。这循环体执行过程中,huge添加到了list中,垃圾回收机制并不回回收huge中的字符串。
随着实例的增多,占用的内存空间也将增多,这就是导致出现问题的原因。
目前,很多系统依然采用1.6及其以下的JDK版本,所以,使用substring时还是需要小心点的。 不过,一般情况下,这种问题很少发生。
1 楼
lbfhappy
2013-08-22
我这里jdk1.7的源码里面是看不到任何内存泄漏的可能的啊。
以上是String的构造方法,这里的value并没有公用,而是调用Arrays.copyOfRange的方法拷贝出了需要用的char的数组,而不是直接使用原来的。
并且就算是直接使用原来的,共用同一个引用的话,那更是一点问题都没有啊,就算是再多的引用指向,也不会增加多少内存啊,只是多一个引用而已,又不会多很多个char[]的内存分配。
不知道楼主是在什么版本的JDK下面测试出来的结果的。
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); }
以上是String的构造方法,这里的value并没有公用,而是调用Arrays.copyOfRange的方法拷贝出了需要用的char的数组,而不是直接使用原来的。
并且就算是直接使用原来的,共用同一个引用的话,那更是一点问题都没有啊,就算是再多的引用指向,也不会增加多少内存啊,只是多一个引用而已,又不会多很多个char[]的内存分配。
不知道楼主是在什么版本的JDK下面测试出来的结果的。
发表评论
-
工厂类中移除if/else语句
2016-07-10 19:52 897面向对象语言的一个强大的特性是多态,它可以用来在代码中移除 ... -
Java编程练手100题
2014-12-11 17:13 6725本文给出100道Java编程练手的程序。 列表如下: 面 ... -
数组复制的三种方法
2014-11-30 12:57 2210本文将给出三种实现数组复制的方法 (以复制整数数组为例)。 ... -
数组复制的三种方法
2014-11-30 12:54 0本文将给出三种实现数组复制的方法 (以复制整数数组为例)。 ... -
四种复制文件的方法
2014-11-29 13:21 1736尽管Java提供了一个类ava.io.File用于文件的操 ... -
判断一个字符串中的字符是否都只出现一次
2014-11-25 12:58 2722本篇博文将给大家带来几个判断一个字符串中的字符是否都只出现一 ... -
使用正则表达式判断一个数是否为素数
2014-11-23 13:35 2163正则表达式能够用于判断一个数是否为素数,这个以前完全没有想过 ... -
几个可以用英文单词表达的正则表达式
2014-11-21 13:12 3741本文,我们将来看一下几个可以用英文单词表达的正则表达式。这些 ... -
(广度优先搜索)打印所有可能的括号组合
2014-11-20 11:58 1953问题:给定一个正整n,作为括号的对数,输出所有括号可能 ... -
随机产生由特殊字符,大小写字母以及数字组成的字符串,且每种字符都至少出现一次
2014-11-19 14:48 3976题目:随机产生字符串,字符串中的字符只能由特殊字符 (! ... -
找出1到n缺失的一个数
2014-11-18 12:57 3168题目:Problem description: You h ... -
EnumSet的几个例子
2014-11-14 16:24 8745EnumSet 是一个与枚举类型一起使用的专用 Set 实现 ... -
给定两个有序数组和一个指定的sum值,从两个数组中各找一个数使得这两个数的和与指定的sum值相差最小
2014-11-12 11:24 3322题目:给定两个有序数组和一个指定的sum值,从两个数组 ... -
Java面试编程题练手
2014-11-04 22:49 6698面试编程 写一个程序,去除有序数组中的重复数字 编 ... -
Collections用法整理
2014-10-22 20:55 9844Collections (java.util.Collect ... -
The Code Sample 代码实例 个人博客开通
2014-09-04 18:48 1413个人博客小站开通 http://thecodesample. ... -
Collections.emptyXXX方法
2014-06-08 13:37 2141从JDK 1.5开始, Collections集合工具类中预先 ... -
这代码怎么就打印出"hello world"了呢?
2014-06-08 00:37 7390for (long l = 4946144450195624L ... -
最短时间过桥
2014-04-21 22:03 4128本文用代码实现最短时间过桥,并且打印如下两个例子的最小过桥时间 ... -
将数组分割成差值最小的子集
2014-04-20 22:34 2898本文使用位掩码实现一个功能 ==》将数组分割成差值最小的子集 ...
相关推荐
下面我们将详细介绍 Java 中由 substring 方法引发的内存泄漏问题的原因和解决方法。 一、内存泄漏的定义 内存泄漏(leak of memory)是指为一个对象分配内存之后,在对象已经不在使用时未及时的释放,导致一直...
在JDK的不同版本中,`String`类经历了一些优化和改进,尤其是在性能和内存管理方面。这里我们将对JDK 1.6、1.7和1.8中的`String`类进行对比研究,探讨其中的关键变化。 ### JDK 1.6中的String 在JDK 1.6中,`...
通过分析`stringUtil.c`源代码和`stringUtil.h`头文件,我们可以了解一些关键知识点。 1. **自定义字符串数据结构**:在C语言中,字符串通常以字符数组的形式存在。在`stringUtil.h`中,可能定义了一个名为`String`...
总结来说,`String.substring`在不同的JDK版本中有不同的实现,从JDK 1.6到1.7的改进主要是为了防止因共享字符数组导致的内存泄漏。在编写涉及大量字符串操作的代码时,理解这些细节可以帮助我们更好地控制内存使用...
在 JDK 6 中,使用 substring 方法可能会出现存储空间的浪费,甚至可能造成内存泄漏。在 JDK 7 中,这个问题已经被解决。 Java substring 方法的实现原理是不同的版本的 JDK 中实现方式各不相同。了解这些差异可以...
7. **源码分析**:对于开源语言,如C++标准库中的`std::string`,深入源码可以帮助理解其内部实现机制,如字符串的内存管理策略、效率优化等。 8. **字符串工具**:在开发中,可能会用到一些工具库,如C++的Boost库...
了解内存使用情况后,开发者可以针对代码进行优化,减少不必要的内存分配,例如及时释放不再使用的对象,避免内存泄漏,或者利用内存池等机制提高内存利用率。 总结起来,通过C#的`System.Diagnostics.Process`类...
总之,编写自己的string库函数是一个挑战性的任务,它涵盖了内存管理、数据结构、算法、异常处理等多个核心编程概念,同时也是深入理解和掌握C++语言的好方法。你的实践不仅锻炼了编程技巧,也证明了你对C++标准库的...
从JDK 7开始,substring()方法会创建一个新的char[]数组,这样可以避免潜在的内存泄漏。 7. String、StringBuilder与StringBuffer的对比 StringBuilder是可变的,适用于频繁修改字符串内容的场景。StringBuffer也是...
9. **内存管理**:由于每次修改都会创建新对象,过多的`String`操作可能导致内存泄漏。因此,在处理大量字符串操作时,需要注意性能优化。 10. **垃圾回收**:不可变性也有助于垃圾回收,因为当一个`String`对象...
6. **优化和替代方案**:对于内存敏感的项目,可以考虑使用字符数组(C 风格字符串)和指针,或者利用 `split()` 函数的返回值,避免创建过多的 `String` 对象。另外,还可以使用 `char*` 和 `strtok()` 函数从 C ...
但是,如果String与其他对象组合形成引用链,可能会导致内存泄漏,这时需要注意及时解除引用,以便垃圾回收器能正确工作。 总之,Java中的String类是一个功能强大且基础的类,理解其特性和使用技巧对于提升编程效率...
在实际项目中,正确使用`std::string`可以避免许多内存泄漏和安全性问题。 总之,`std::string`是C++中处理字符串的标准工具,具有高效、灵活和安全的特点。通过学习这个实例教程,你可以掌握如何创建、修改、查询...
例如,在JDK6中,String的substring()方法在创建新字符串时,没有正确地解除对原字符串部分字符的引用,导致了内存泄漏。这个问题在JDK7中得到了修复。 防止内存泄漏的一些策略包括: 1. 适时清理集合类,避免使用...
1. **内存管理**:`string`类内部自动管理内存,程序员无需担心内存溢出或泄漏问题。 2. **安全性**:`string`类提供了丰富的接口来确保安全地访问和修改字符串内容。 3. **便捷性**:`string`类支持多种操作,如...
应使用现有的对象数据,如利用substring而不是创建新String对象,直接将字符串附加到StringBuffer中而非创建临时对象,以及优先使用基本数据类型数组代替包装类数组。 2. 使用本地方法:对于字符串处理等性能敏感...
### 面向对象知识点详解 #### 一、引用数据的操作深入 在Java中,数据类型分为两大类:基本数据类型和引用数据类型。其中,引用数据类型包括类...同时,熟悉String类的特性和方法也是日常开发中必不可少的一部分。
- `HeapOutOfMemory`: 增加堆空间大小,优化内存使用策略。 - `YoungOutOfMemory`: 调整年轻代大小,优化GC策略。 - `MethodAreaOutOfMemory`: 增大PermGen或Metaspace大小。 - `ConstantPoolOutOfMemory`: 减少...