Java6 String.substring()方法的内存泄露
substring(start,end)在Java编程里面经常使用,没想到如果使用不当,会出现内存泄露。
要了解substring(),最好的方法便是查看源码(jdk6):
/**
* <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()的源代码,为如何编写api提供了很好的一个例子,让我想起了老赵的一篇文章,对参数的判断,异常的处理,思路上有点接近。
值得注意的是,如果调用substring(i,i)的话(即beginIndex==endIndex)或者是substring(stringLength)(即是beginIndex==字符串长度),并不会抛出异常,而是会返回一个空的字符串,因为new String(offset + beginIndex , 0 , value)。
言归正传,真正创建字符串的,是一个String(int,in,char[])的构造函数,源代码如下:
// Package private constructor which shares value array for speed.
String(int offset, int count, char value[]) {
this.value = value;
this.offset = offset;
this.count = count;
}
Java里的字符串,其实是由三个私有变量定义:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence
{
/** The value is used for character storage. */
private final char value[]; /** The offset is the first index of the storage that is used. */
private final int offset; /** The count is the number of characters in the String. */
private final int count;
}
当为字符串分配内存时,char数组存储字符,offset=0,count=字符串长度。问题在于,由substring(start,end)调用构造函数String(int,in,char[])时,实际上是改变offset和count的位置达到取得子字符串的目的,而子字符串里的value[]数组,仍然指向原字符串。假设原字符串s有1GB,且我们需要的是s.substring(1,10)这样一段小的字符串,但由于substring()里的value[]数组仍然指向1GB的原字符串,导致原字符串无法在GC中释放,从而产生了内存泄露。
但为什么要这样设计呢?由于String是不可变的(immutable),基于这种共享同一个字符数组的设计有以下好处:
调用substring()时无需复制数组,可重用value[]数组;且substring()的运行是常数时间,非线性,性能得到提高(这也是第二段代码注释的意思:share values for speed)。
而劣势,便是可能会产生内存泄露(实际上,Oracle早有人提出这个bug:http://bugs.sun.com/view_bug.do?bug_id=4513622)。
如何避免这个问题呢?有一个变通的方案,通过一个构造函数,复制一段数组:
/**
* Initializes a newly created {@code String} object so that it represents
* the same sequence of characters as the argument; in other words, the
* newly created string is a copy of the argument string. Unless an
* explicit copy of {@code original} is needed, use of this constructor is
* unnecessary since Strings are immutable.
*
* @param original
* A {@code String}
*/
public String(String original) {
int size = original.count;
char[] originalValue = original.value;
char[] v;
if (originalValue.length > size) {
// The array representing the String is bigger than the new
// String itself. Perhaps this constructor is being called
// in order to trim the baggage, so make a copy of the array.
int off = original.offset;
v = Arrays.copyOfRange(originalValue, off, off+size);
} else {
// The array representing the String is the same
// size as the String, so no point in making a copy.
v = originalValue;
}
this.offset = 0;
this.count = size;
this.value = v;
} //smalStr no longer holds the value[] of 1GB
String smallStr = new String(s.substring(1,10));
上面的构造方法,重新复制了一段数组给v,然后再将v给字符串的数组,从而避免内存泄露。
在Java7里,String的实现已经改变,substring()方法的实现,由原来的共享数组变成了传统的拷贝,杜绝了内存泄露的同时也将运行时间由常数变成了线性:
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > value.length) {
throw new StringIndexOutOfBoundsException(endIndex);
}
int subLen = endIndex - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return ((beginIndex == 0) && (endIndex == value.length)) ? this
: new String(value, beginIndex, subLen);
}
/**
* Allocates a new {@code String} that contains characters from a subarray
* of the character array argument. The {@code offset} argument is the
* index of the first character of the subarray and the {@code count}
* argument specifies the length of the subarray. The contents of the
* subarray are copied; subsequent modification of the character array does
* not affect the newly created string.
*
* @param value
* Array that is the source of characters
*
* @param offset
* The initial offset
*
* @param count
* The length
*
* @throws IndexOutOfBoundsException
* If the {@code offset} and {@code count} arguments index
* characters outside the bounds of the {@code value} array
*/
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);
}
这个构造函数,每次都会复制数组,实现与Java6并不一样。至于哪个好哪个坏,其实很难说清楚。
据说有一种Rope的数据结构,可以更加高效地处理字符串,得好好看看。
参考:
http://javarevisited.blogspot.hk/2011/10/how-substring-in-java-works.html
http://eyalsch.wordpress.com/2009/10/27/stringleaks/
http://blog.zhaojie.me/2013/03/string-and-rope-1-string-in-dotnet-and-java.html
http://www.transylvania-jug.org/archives/5530
Java6 String.substring()方法的内存泄露的更多相关文章
- C#--String.Substring方法
第一种:String.SubString(int start,int length) 截取指定长度的字符串 这里有两个int型的参数 string表示字符串截取的起始位置,length表截取的 ...
- Delphi结构中使用String时遇到的内存泄露问题(没有利用String的引用计数自动销毁字符串的功能)
先定义一个结构: TUserInfo = record UserID: Integer; // 用户编号 UserName: string; // 用户名end; 然后编写如下代码: proced ...
- String中substring方法内存泄漏问题
众所周知,JDK中以前String类中的substring方法存在内存泄漏问题,之所以说是以前,是因为JDK1.7及以后的版本已经修复了,我看都说JDK1.6的版本也存在这个问题,但是我本机上安装的1 ...
- Java常见问题分析(内存溢出、内存泄露、线程阻塞等)
Java垃圾回收机制(GC) 1.1 GC机制作用 1.2 堆内存3代分布(年轻代.老年代.持久代) 1.3 GC分类 1.4 GC过程 Java应用内存问题分析 2.1 Java内存划分 2.2 J ...
- 强大的windbg定位内存泄露,两句命令搞定!
1.简单配置在windbg程序目录下有个gflags.exe,运行后设置: 运行CMD.EXE,输入"D:\Debugging Tools for Windows (x86)\gflags. ...
- 项目问题总结:Block内存泄露 以及NSTimer使用问题
BLock的内存泄露 在我们代码中关于block的使用可以说随处可见,第一次接触block的时候是关于UIView的块动画,那时觉得block的使用好神奇,再后来分析总结为block其实就是一个c语言 ...
- String.IndexOf String.IndexOf String.Substring
String.IndexOf String.IndexOf 方法 (Char, Int32, Int32)报告指定字符在此实例中的第一个匹配项的索引.搜索从指定字符位置开始,并检查指定数量的字符位置. ...
- ARC下的内存泄露
iOS提供了ARC功能,很大程度上简化了内存管理的代码. 但使用ARC并不代表了不会发生内存泄露,使用不当照样会发生内存泄露. 下面列举两种ARC导致内存泄露的情况. 1,循环参照 A有个属性参照B, ...
- dotnet 6 在 Win7 系统证书链错误导致 HttpWebRequest 内存泄露
本文记录我将应用迁移到 dotnet 6 之后,在 Win7 系统上,因为使用 HttpWebRequest 访问一个本地服务,此本地服务开启 https 且证书链在此 Win7 系统上错误,导致应用 ...
随机推荐
- 我的android学习经历12
自动匹配输入的内容(文章最后有一个问题有兴趣的可以解答一下,谢谢大神了) 这个主要是两个控件MultiAutoCompleteTextView和AutoCompleteTextView 这两个控件和T ...
- iOS网络-01-NSURLRequest与NSURLConnection
NSURLRequest NSURLRequest封装了一次网络请求所需要的数据,主要封装了以下信息: 请求路径(URL) 请求方法(GET或POST) 请求头 请求体 超时参数 NSURLReque ...
- IOS设计模式-简单工厂
为什么 “简单工厂原理” 叫简单工厂,因为这个工厂类不是抽象类,而是实体类,工厂本身就是一个具体的对象. 写一个例子演示简单工厂模式: >>>>>>>> ...
- Android 之 自动匹配字符AutoCompleteTextView
AutoCompleteTextView是自动匹配字符,当我们输入一个单词或一段话的前几个字时,就会自动为你匹配后面的内容看效果图: 下面是代码: MainActivit: package com.e ...
- spring 管理 jdbc 事务
@Transactional 业务实现类 类名上方--这个类中的方法,执行操作前会打开事务. 默认:RuntimeException 自动回滚, 可以try catch 的异常,不会滚 方法名 ...
- 从1.5k到18k, 一个程序员的5年成长之路
昨天收到了心仪企业的口头offer, 回首当初什么都不会开始学编程, 到现在恰好五年. 整天在社区晃悠, 看了不少的总结, 在这个时间点, 我也写一份自己的总结吧. 我一直在社区分享, 所以, 这篇总 ...
- OOD沉思录 --- 类和对象的关系 --- 包含关系1
4.5 如果类包含另一个类的对象,那么包含类应当向被包含的对象发送消息(调用方法). 也就是说,所有的包含关系都应当是使用关系. 如果不是这样,那么包含的类有什么用处呢?当然,面向过程的开发人员会想 ...
- 深入剖析js命名空间函数namespace
在看阿里员工写的开源数据库连接池的druid的源代码时,发现了其中在jquery的原代码中又定义了一个命名空间的函数:$.namespace(),其代码如下: 网址为:https://github.c ...
- jQuery Validate 表单验证插件----自定义一个验证方法
一.下载依赖包 网盘下载:https://yunpan.cn/cryvgGGAQ3DSW 访问密码 f224 二.引入依赖包 <script src="../../scripts/j ...
- Google 以图搜图 - 相似图片搜索原理 - Java实现
前阵子在阮一峰的博客上看到了这篇<相似图片搜索原理>博客,就有一种冲动要将这些原理实现出来了. Google "相似图片搜索":你可以用一张图片,搜索互联网上所有与它相 ...