文档章节

String substring的内存泄漏分析

王孟君
 王孟君
发布于 2017/03/25 10:01
字数 1478
阅读 129
收藏 0

String类的substring方法,为我们截取子字符串提供了便捷,但同时,如果使用的JDK是1.6版本,则使用不当的话可能导致内存泄露

String#substring源码分析

本示例查看的是JDK1.6版本的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[])将继续重用

new String(offset + beginIndex, endIndex - beginIndex, value);

这种方式提高了运算速度却要在内存中保留原来字符串的内容。 

substring内存泄露示例

示例 -->

读取一个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();
	}
}

避免方法

创建新的字符串

	/**
	 * 采用新建的方式,避免在内存中占有较多的内容。
	 */
	public String subString2(int begin, int end) {
		return new String(str.substring(begin, end));
	}

使用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)运行程序,得到的结果如下: 

  • 采用huge.subString1(0, i)遇到OutOfMemoryError 

  • 采用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()方法使用有其局限性。这个只有在从大字符串中截取比较小的子字符串,并且原来的字符串不需要再继续使用的场景下有较好的作用。 

JDK 1.7之后,已经对这快代码进行了优化和调整,JDK1.7中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);
    }

这里的value并没有公用,而是调用Arrays.copyOfRange的方法拷贝出了需要用的char的数组,而不是直接使用原来的。

© 著作权归作者所有

王孟君

王孟君

粉丝 227
博文 94
码字总数 221044
作品 0
杭州
高级程序员
私信 提问
Java字符串之JDK 6和JDK 7中substring的原理及区别

String是Java中一个比较基础的类,每一个开发人员都会经常接触到。而且,String也是面试中经常会考的知识点。String有很多方法,有些方法比较常用,有些方法不太常用。今天要介绍的subString...

郑加威
2017/02/27
0
0
介绍 Java 的内存泄漏

java最明显的一个优势就是它的内存管理机制。你只需简单创建对象,java的垃圾回收机制负责分配和释放内存。然而情况并不像想像的那么简单,因为在Java应用中经常发生内存泄漏。 本教程演示了...

oschina
2013/10/21
1K
0
Cocos开发中性能优化工具介绍之Xcode中Instruments工具使用

Instruments是动态分析工具,它与Xcode集成在一起,可以在Xcode中通过菜单Product→Profile启动。启动如图所示,Instruments有很多跟踪模板可以动态分析和跟踪内存、CPU和文件系统。 每个跟踪...

智捷课堂
2014/11/05
0
0
Java 程序优化:字符串操作、基本运算方法等优化策略(一)

针对 Java 程序编写过程中的实际问题,本文分为两部分,首先对字符串相关操作、数据切分、处理超大 String 对象等提出解决方案及优化建议,并给出具体代码示例;然后对数据定义、运算逻辑优化...

Mysoft
2015/09/25
46
0
JDK7和JDK6中substring()的不同

String substring(int beginIndex, int endIndex) 返回原字符串的子字符串这方法,只要是稍微了解点java的人都知道,就像知道1+1==2一样简单。不过其中的猫腻很少有人关注,就像基本没人问1...

浪子_仗剑走天涯
2013/11/21
0
9

没有更多内容

加载失败,请刷新页面

加载更多

分享一波 RabbitMQ 面试题有答案

1、什么是rabbitmq 2、为什么要使用rabbitmq 3、使用rabbitmq的场景 4、如何确保消息正确地发送至RabbitMQ? 如何确保消息接收方消费了消息? 发送方确认模式 接收方确认机制 接收方消息确认...

搜云库技术团队
59分钟前
2
0
2019年JAVA面试题(高级资深)

记录下本年度最新的面试题: 2019-04-24 //某互联网公司,劳工资源管理方向职位 1.bio/nio/aio介绍下,粘包、拆包问题怎么解决? 2.数据库四个特性是什么,事务传播性是怎么样的?spring事务和...

em_aaron
今天
2
0
yarn如何全局安装命令以及和环境变量的关系

npm全局安装 npm i -g xxx yarn 全局安装 yarn global add xxx 然而你可能会发现npm全局安装后的命令可以直接使用,而yarn却不行,这是为什么呢? 我们来查看下npm和yarn的bin目录 使用npm全...

单线程生物
今天
2
0
异步线程RequestContextHolder.getRequestAttributes()为null

使用Spring框架,在Service中开启一个新的线程,在新的线程中使用 RequestAttributes ra = RequestContextHolder.getRequestAttributes(); 获取出来为null,有没有什么办法能解决? 问题出现...

xiaomin0322
今天
1
0
mingw64环境搭建

mingw64环境搭建 转自:http://www.cr173.com/soft/132367.html MinGW64位版,默认编译出来是64位的,需要编译32位请使用-m32 参数!mingw是一款gnu工具集合是Minimalist GNU on Windows的简称...

shzwork
今天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部