本文将对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()方法使用有其局限性。这个只有在从大字符串中截取比较小的子字符串,并且原来的字符串不需要再继续使用的场景下有较好的作用。
已有 0人发表留言,猛击->> 这里<<-参与讨论
ITeye推荐
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()方法使用有其局限性。这个只有在从大字符串中截取比较小的子字符串,并且原来的字符串不需要再继续使用的场景下有较好的作用。
已有 0人发表留言,猛击->> 这里<<-参与讨论
ITeye推荐