- 浏览: 466228 次
- 性别:
- 来自: 杭州
文章分类
最新评论
-
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 903面向对象语言的一个强大的特性是多态,它可以用来在代码中移除 ... -
Java编程练手100题
2014-12-11 17:13 6729本文给出100道Java编程练手的程序。 列表如下: 面 ... -
数组复制的三种方法
2014-11-30 12:57 2213本文将给出三种实现数组复制的方法 (以复制整数数组为例)。 ... -
数组复制的三种方法
2014-11-30 12:54 0本文将给出三种实现数组复制的方法 (以复制整数数组为例)。 ... -
四种复制文件的方法
2014-11-29 13:21 1741尽管Java提供了一个类ava.io.File用于文件的操 ... -
判断一个字符串中的字符是否都只出现一次
2014-11-25 12:58 2724本篇博文将给大家带来几个判断一个字符串中的字符是否都只出现一 ... -
使用正则表达式判断一个数是否为素数
2014-11-23 13:35 2168正则表达式能够用于判断一个数是否为素数,这个以前完全没有想过 ... -
几个可以用英文单词表达的正则表达式
2014-11-21 13:12 3750本文,我们将来看一下几个可以用英文单词表达的正则表达式。这些 ... -
(广度优先搜索)打印所有可能的括号组合
2014-11-20 11:58 1954问题:给定一个正整n,作为括号的对数,输出所有括号可能 ... -
随机产生由特殊字符,大小写字母以及数字组成的字符串,且每种字符都至少出现一次
2014-11-19 14:48 3976题目:随机产生字符串,字符串中的字符只能由特殊字符 (! ... -
找出1到n缺失的一个数
2014-11-18 12:57 3176题目:Problem description: You h ... -
EnumSet的几个例子
2014-11-14 16:24 8749EnumSet 是一个与枚举类型一起使用的专用 Set 实现 ... -
给定两个有序数组和一个指定的sum值,从两个数组中各找一个数使得这两个数的和与指定的sum值相差最小
2014-11-12 11:24 3327题目:给定两个有序数组和一个指定的sum值,从两个数组 ... -
Java面试编程题练手
2014-11-04 22:49 6700面试编程 写一个程序,去除有序数组中的重复数字 编 ... -
Collections用法整理
2014-10-22 20:55 9846Collections (java.util.Collect ... -
The Code Sample 代码实例 个人博客开通
2014-09-04 18:48 1418个人博客小站开通 http://thecodesample. ... -
Collections.emptyXXX方法
2014-06-08 13:37 2145从JDK 1.5开始, Collections集合工具类中预先 ... -
这代码怎么就打印出"hello world"了呢?
2014-06-08 00:37 7398for (long l = 4946144450195624L ... -
最短时间过桥
2014-04-21 22:03 4145本文用代码实现最短时间过桥,并且打印如下两个例子的最小过桥时间 ... -
将数组分割成差值最小的子集
2014-04-20 22:34 2903本文使用位掩码实现一个功能 ==》将数组分割成差值最小的子集 ...
相关推荐
通过分析`stringUtil.c`源代码和`stringUtil.h`头文件,我们可以了解一些关键知识点。 1. **自定义字符串数据结构**:在C语言中,字符串通常以字符数组的形式存在。在`stringUtil.h`中,可能定义了一个名为`String`...
了解内存使用情况后,开发者可以针对代码进行优化,减少不必要的内存分配,例如及时释放不再使用的对象,避免内存泄漏,或者利用内存池等机制提高内存利用率。 总结起来,通过C#的`System.Diagnostics.Process`类...
理解字符串的内存管理对于避免内存泄漏和提高程序性能至关重要。 5. **字符串常量与变量**:字符串可以是常量(不可改变)或变量(可改变)。在C/C++中,字符串常量是字符数组的别名,而Java和Python则有特殊的字符...
- **熟悉String的使用**: 如何正确使用`String`类的各种方法,如`equals()`、`indexOf()`、`substring()`等。 - **熟悉Java关键字**: 了解Java语言的关键字及其用途,如`final`、`static`、`abstract`等。 - **集合...
17. **性能优化**:学习如何分析和优化Java应用程序的性能,包括JVM参数调整、内存管理和代码优化技巧。 通过这些教程,你将能够全面掌握Java SE编程的基本技能,并具备解决实际问题的能力。由于Java是一种广泛应用...
10. **Java的内存管理与垃圾收集**: 理解Java的内存模型,尤其是堆和栈的区别,以及垃圾收集的工作原理,对于优化代码性能和避免内存泄漏至关重要。 11. **Java 8及以上版本的新特性**: 比如Lambda表达式、Stream ...
4. **字符串操作**:Java中的`String`类有许多方法,如`concat()`, `substring()`, `trim()`等,理解它们的用法和行为差异是必要的。 5. **数组与集合**:Java中的数组和集合(如ArrayList, HashSet, HashMap等)的...
21. **性能优化**:学习如何分析和优化Java程序的性能,包括垃圾回收、内存泄漏和代码优化技巧。 "day13"文件可能包含第13天学习的具体代码示例和练习,涵盖上述知识点的一个或多个部分。通过详细阅读和实践这些...
1. String类:深入理解字符串的不可变性,字符串连接的性能优化,以及常用的方法如substring、indexOf、replace等。 2. Date和Calendar类:了解日期时间处理,如何进行日期的计算和格式化。 3. Reflection API:...
2. **垃圾回收机制**:了解GC的基本原理,包括Minor GC和Major GC,以及如何避免内存泄漏。 3. **设计模式**:单例、工厂、观察者等23种设计模式,是解决常见软件设计问题的模板。 4. **Spring框架**:学习依赖...
- 代码优化:学习减少DOM操作、避免内存泄漏、使用缓存等技巧。 - SEO优化:理解网页对搜索引擎的友好性,如元标签的使用。 10. **浏览器工作原理** - 渲染过程:了解浏览器的解析HTML、构建DOM树、CSSOM树,到...
9. **字符串处理**:熟练掌握String类的常用方法,如Substring、IndexOf、Replace、Trim等,以及StringBuilder类在性能优化中的应用。 10. **文件和流**:知道如何读写文件,理解文件流的概念,以及如何使用...
垃圾回收(GC)是Java运行时环境的一部分,负责自动管理内存,回收不再使用的对象所占用的空间,避免程序因内存泄漏而导致崩溃。GC的触发时机、回收策略和算法对程序性能有重要影响。 #### 12. Java Web基础知识 -...
深入理解String的源码对于提升Java开发技能和优化代码性能至关重要。在这个CSP项目中,我们可以找到对Java 6版本中String类的源码解析,这对于我们学习Java内部机制,特别是字符串操作的底层实现,具有很大的帮助。 ...
14. **性能优化**:分析和解决Java程序的性能问题,如内存泄漏、CPU占用过高、GC调优等。 15. **并发编程**:深入理解并发编程的相关概念,如并发容器、线程池、并发工具类等。 16. **Spring框架**:学习使用...
JDK6和JDK7中的substring()方法 - **方法演变**:比较两个版本中`substring()`方法的差异。 - **性能改进**:JDK7中对substring()进行了优化,减少了内部拷贝操作,提高了效率。 - **应用场景**:探讨在不同场景下...
- Java中字符串(String)是一个不可变的对象,可以使用String类的各种方法进行字符串操作,如concat()、equals()、substring()等。 - **1.1.4 数组** - 数组是一种基本的数据结构,用于存储相同类型的元素。Java...
10. **JVM内存模型**:理解堆内存、栈内存、方法区、本地方法栈的概念,以及垃圾回收机制(GC),能够分析内存泄漏和性能优化。 11. **Java 8及以后的新特性**:包括Lambda表达式、流API(Stream)、Optional类、...
- **安全性**:Java提供了严格的内存管理机制,减少了内存泄漏和缓冲区溢出的风险。 - **健壮性**:Java强调“一次编写,到处运行”,确保了程序的稳定性。 - **性能优化**:随着JIT编译器的发展,Java的性能得到了...
- 内存区域包括堆、栈、方法区、本地方法栈等,了解其工作原理有助于避免内存泄漏。 15. **JVM(Java虚拟机)** - JVM是Java程序的运行环境,负责解析字节码并执行。 - 了解JVM的工作机制,如类加载、内存模型、...