String substring的内存泄漏分析和优化方法

标签: string substring 内存泄漏 | 发表时间:2013-08-21 23:26 | 作者:
出处:http://www.iteye.com
本文将对String.substring方法可能产生内存泄漏的问题进行分析,并给出相应的优化方法。

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推荐



相关 [string substring 内存泄漏] 推荐:

String substring的内存泄漏分析和优化方法

- - ITeye博客
本文将对String.substring方法可能产生内存泄漏的问题进行分析,并给出相应的优化方法. String.substring内存泄漏分析. 首先看一下JDK6 String.substring的源代码:. 从上述的源代码可以看出,使用substring获取子字符串方法中,原有字符串的内容value(char[])将继续重用.

内存泄漏

- - CSDN博客系统运维推荐文章
程序申请了堆空间,但是“忘记”释放,导致该块区域在程序结束前无法被再次使用导致的. 泄漏时间长了,就会导致用户空间内存不足,严重的导致死机. 如果泄漏比较严重,很容易察觉;但是有些泄漏很缓慢,不容易察觉,但是软件会运行很长时间后,会慢慢导致严重问题,而且当发现症状的时候,基本上已经是比较晚的时候了,想要识别泄漏,还是可以实现的,本篇文章来聊聊内存操作的原理.

String 常量池和 String#intern()

- - ImportNew
String是Java基础的重要考点. 可问的点多,而且很多点可以横向切到其他考点,或纵向深入JVM. 本文略过了String的基本内容,重点在于String#intern(). String常量可能会在两种时机进入常量池:. 编译期:通过双引号声明的常量(包括 显示声明、 静态编译优化后的常量,如”1”+”2”优化为常量”12”),在前端编译期将被静态的写入class文件中的“常量池”.

java内存泄漏

- - 编程语言 - ITeye博客
不论哪种语言的内存分配方式,都需要返回所分配内存的真实地址,也就是返回一个指针到内存块的首地址. Java中对象是采用new或者反射的方法创建的,这些对象的创建都是在堆(Heap)中分配的,所有对象的回收都是由Java虚拟机通过垃圾回收机制完成的. GC为了能够正确释放对象,会监控每个对象的运行状况,对他们的申请、引用、被引用、赋值等状况进行监控,Java会使用有向图的方法进行管理内存,实时监控对象是否可以达到,如果不可到达,则就将其回收,这样也可以消除引用循环的问题.

浅谈Java--内存泄漏

- - ITeye博客
      JAVA的垃圾回收机制,让许多程序员觉得内存管理不是很重要,但是内存内存泄露的事情恰恰这样的疏忽而发生,特别是对于Android开发,内存管理更为重要,养成良好的习惯,有利于避免内存的泄漏..     这里可以把许多对象和引用看成是有向图,顶点可以是对象也可以是引用,引用关系就是有向边.

Android 解析内存泄漏

- - CSDN博客移动开发推荐文章
1、引用没释放造成的内存泄露.        1.1、注册没取消造成的内存泄露.        这种 Android的内存泄露比纯 Java的内存泄露还要严重,因为其他一些Android程序可能引用我们的Anroid程序的对象(比如注册机制). 即使我们的Android程序已经结束了,但是别的引用程序仍然还有对我们的Android程序的某个对象的引用,泄露的内存依然不能被垃圾回收.

String与InputStream互相转换

- - BlogJava_首页
原文: http://www.heatpress123.net/cpzs/.

String压缩 解压缩

- - CSDN博客推荐文章
数据传输时,有时需要将数据压缩和解压缩,本例使用GZIPOutputStream/GZIPInputStream实现. 1、使用ISO-8859-1作为中介编码,可以保证准确还原数据. 2、字符编码确定时,可以在decompress方法最后一句中显式指定编码. * @return 压缩后的字符串. GZIPOutputStream os = null; // 使用默认缓冲区大小创建新的输出流.

shared_ptr真能防止内存泄漏吗?

- Roger - codedump
这个命题有些诡异,因为shared_ptr设计的初衷就是为了防止内存泄漏,但是先别急,等我把问题描述清楚.. 事出缘由是这几天项目出现一个内存泄漏的bug,之前这部分是使用shared_ptr封装了很多指针的操作,后来出于效率的考虑,改回了裸指针.由于我们使用的google tcmalloc做内存分配,它自带了检测内存泄漏的功能,于是在单元测试的时候就被检查出了内存泄漏..

(转)ThreadLocal的内存泄漏问题

- - 编程语言 - ITeye博客
原文:http://www.godiscoder.com/?p=479. 在最近一个项目中,在项目发布之后,发现系统中有内存泄漏问题. 表象是堆内存随着系统的运行时间缓慢增长,一直没有办法通过gc来回收,最终于导致堆内存耗尽,内存溢出. 开始是怀疑ThreadLocal的问题,因为在项目中,大量使用了线程的ThreadLocal保存线程上下文信息,在正常情况下,在线程开始的时候设置线程变量,在线程结束的时候,需要清除线程上下文信息,如果线程变量没有清除,会导致线程中保存的对象无法释放.