Java中的substring 内存泄露真的会引起内存泄露么

问题对人有帮助,内容完整,我也想知道答案
问题没有实际价值,缺少关键内容,没有改进余地
JDK6中的subString方法会存在内存泄露问题,是由于源字符串中的value数组不会被GC回收。看到一篇文章(),里面有如下代码,说在JDK6下会报内存溢出的错误。请问下,为什么会产生?list的每次add不是只加一点点内存占用么?
public class SubMain {
private String strs = new String(new byte[100000]);
String getString() {
return this.strs.substring(0, 2);
public static void main(String[] args) {
List&String& list = new ArrayList&String&();
SubMain sub = new SubMain();
for (int i = 0; i & 1000000; i++) {
list.add(sub.getString());
/*Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.lang.StringCoding$StringDecoder.decode(StringCoding.java:133)
at java.lang.StringCoding.decode(StringCoding.java:173)
at java.lang.StringCoding.decode(StringCoding.java:185)
at java.lang.String.&init&(String.java:570)
at java.lang.String.&init&(String.java:593)
at com.jd.o2o.substring.SubMain.&init&(SubMain.java:8)
at com.jd.o2o.substring.SubMain.main(SubMain.java:18)*/
其他参考文章:,
答案对人有帮助,有参考价值
答案没帮助,是错误的答案,答非所问
前面的童鞋也讲的很明白了,即使是jdk1.6的方式也不会存在你所说的内存泄露问题。导致你内存不足的原因是因为你本身就没有足够的内存申请1000000个new String(new byte[100000])对象。
还有,按照你的逻辑来实现的话,JDK1.6的实现方式绝对是占用更少内存的,因为array是引用类型。即便你循环1000000次substring,在String对象中的char[] value也是同一个对象,而JDK1.7使用Arrays.copy的方式实现,那你每一次substring就将会生成一个新的char[] value实例。
答案对人有帮助,有参考价值
答案没帮助,是错误的答案,答非所问
你需要先把java SubMain sub = new SubMain();提到for循环外面初始化。因为你每次for循环都在堆上创建SubMain,每个SubMain都至少占用了100k,你重复1000000次,这得需要100G的堆空间。同时list里面保存了SubMain的引用,gc和full gc都无法回收,肯定会内存溢出!
同时,讨论内存分配,至少要把虚拟机配置的-Xms,-Xmx, -Xmn, -XX:SurvivorRatio提供出来啊!
分享到微博?
Hi,欢迎来到 SegmentFault 技术社区!⊙▽⊙ 在这里,你可以提出编程相关的疑惑,关注感兴趣的问题,对认可的回答投赞同票;大家会帮你解决编程的问题,和你探讨技术更新,为你的回答投上赞同票。
明天提醒我
关闭理由:
删除理由:
忽略理由:
推广(招聘、广告、SEO 等)方面的内容
与已有问题重复(请编辑该提问指向已有相同问题)
答非所问,不符合答题要求
宜作评论而非答案
带有人身攻击、辱骂、仇恨等违反条款的内容
无法获得确切结果的问题
非开发直接相关的问题
非技术提问的讨论型问题
其他原因(请补充说明)
我要该,理由是:
扫扫下载 AppJava中的substring真的会引起内存泄露么? - ImportNew
在Java中开发,String是我们开发程序可以说必须要使用的类型,String有一个substring方法用来截取字符串,我们想必也常常使用。但是你知道么,关于Java 6中的substring是否会引起内存泄露,在国外的论坛和社区有着一些讨论,以至于Java官方已经将其标记成bug,并且为此Java 7 还重新进行了实现。读到这里可能你的问题就来了,substring怎么会引起内存泄露呢?那么我们就带着问题,走进小黑屋,看看substring有没有内存泄露,又是怎么导致所谓的内存泄露。
substring方法提供两种重载,第一种为只接受开始截取位置一个参数的方法。
public String substring(int beginIndex)
比如我们使用上面的方法,"unhappy".substring(2) 返回结果 "happy"
另一种重载就是接受一个开始截取位置和一个结束截取位置的参数的方法。
public String substring(int beginIndex, int endIndex)
使用这个方法,"smiles".substring(1, 5) 返回结果 "mile"
通过这个介绍我们基本了解了substring的作用,这样便于我们理解下面的内容。
因为这个问题出现的情况在Java 6,如果你的Java版本号不是Java 6 需要调整一下。
终端调整(适用于Mac系统)
查看java版本号
13:03 $ java -version
java version &1.8.0_25&
Java(TM) SE Runtime Environment (build 1.8.0_25-b17)
Java HotSpot(TM) 64-Bit Server VM (build 25.25-b02, mixed mode)
export JAVA_HOME=$(/usr/libexec/java_home -v 1.6)
Ubuntu使用alternatives --config java,Fedora上面使用alternatives --config java。
如果你使用Eclipse,可以选择工程,右击,选择Properties(属性)— Java Compiler(Java编译器)进行特殊指定。
这里贴一下java官方bug里用到的重现问题的代码。
public class TestGC {
private String largeString = new String(new byte[100000]);
String getString() {
return this.largeString.substring(0,2);
public static void main(String[] args) {
java.util.ArrayList list = new java.util.ArrayList();
for (int i = 0; i & 1000000; i++) {
TestGC gc = new TestGC();
list.add(gc.getString());
然而上面的代码,只要使用Java 6 (Java 7和8 都不会抛出异常)运行一下就会报java.lang.OutOfMemoryError: Java heap space的异常,这说明没有足够的堆内存供我们创建对象,JVM选择了抛出异常操作。
于是有人会说,是因为你每个循环中创建了一个TestGC对象,虽然我们加入ArrayList只是两个字符的字符串,但是这个对象中又存储largeString这么大的对象,这样必然会造成OOM的。
然而,其实你说的不对。比如我们看一下这样的代码,我们只修改getString方法
public class TestGC {
private String largeString = new String(new byte[100000]);
String getString() {
//return this.largeString.substring(0,2);
return new String(&ab&);
public static void main(String[] args) {
java.util.ArrayList list = new java.util.ArrayList();
for (int i = 0; i & 1000000; i++) {
TestGC gc = new TestGC();
list.add(gc.getString());
执行上面的方法,并不会导致OOM异常,因为我们持有的时1000000个ab字符串对象,而TestGC对象(包括其中的largeString)会在java的垃圾回收中释放掉。所以这里不会存在内存溢出。
那么究竟是什么导致的内存泄露呢?要研究这个问题,我们需要看一下方法的实现,即可。
深入Java 6实现
在String类中存在这样三个属性
value 字符数组,存储字符串实际的内容
offset 该字符串在字符数组value中的起始位置
count 字符串包含的字符的长度
Java 6中substring的实现
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);
上述方法调用的构造方法
//Package private constructor which shares value array for speed.
String(int offset, int count, char value[]) {
this.value =
this.offset =
this.count =
当我们读完上述的代码,我们应该会豁然开朗,原来是这个样子啊!
当我们调用字符串a的substring得到字符串b,其实这个操作,无非就是调整了一下b的offset和count,用到的内容还是a之前的value字符数组,并没有重新创建新的专属于b的内容字符数组。
举个和上面重现代码相关的例子,比如我们有一个1G的字符串a,我们使用substring(0,2)得到了一个只有两个字符的字符串b,如果b的生命周期要长于a或者手动设置a为null,当垃圾回收进行后,a被回收掉,b没有回收掉,那么这1G的内存占用依旧存在,因为b持有这1G大小的字符数组的引用。
看到这里,大家应该可以明白上面的代码为什么出现内存溢出了。
共享内容字符数组
其实substring中生成的字符串与原字符串共享内容数组是一个很棒的设计,这样避免了每次进行substring重新进行字符数组复制。正如其文档说明的,共享内容字符数组为了就是速度。但是对于本例中的问题,共享内容字符数组显得有点蹩脚。
对于之前比较不常见的1G字符串只截取2个字符的情况可以使用下面的代码,这样的话,就不会持有1G字符串的内容数组引用了。
String littleString = new String(largeString.substring(0,2));
下面的这个构造方法,在源字符串内容数组长度大于字符串长度时,进行数组复制,新的字符串会创建一个只包含源字符串内容的字符数组。
public String(String original) {
int size = original.
char[] originalValue = original.
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.
v = Arrays.copyOfRange(originalValue, off, off+size);
// The array representing the String is the same
// size as the String, so no point in making a copy.
v = originalV
this.offset = 0;
this.count =
this.value =
Java 7 实现
在Java 7 中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 - beginI
if (subLen & 0) {
throw new StringIndexOutOfBoundsException(subLen);
return ((beginIndex == 0) && (endIndex == value.length)) ? this
: new String(value, beginIndex, subLen);
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);
真的是内存泄露么
我们知道了substring某些情况下可能引起内存问题,但是这个叫做内存泄露么?
其实个人认为这个不应该算为内存泄露,使用substring生成的字符串b固然会持有原有字符串a的内容数组引用,但是当a和b都被回收之后,该字符数组的内容也是可以被垃圾回收掉的。
哪个版本实现的好
关于Java 7 对substring做的修改,收到了褒贬不一的反馈。
个人更加倾向于Java 6的实现,当进行substring时,使用共享内容字符数组,速度会更快,不用重新申请内存。虽然有可能出现本文中的内存性能问题,但也是有方法可以解决的。
Java 7的实现不需要程序员特殊操作避免了本文中问题,但是进行每次substring的操作性能总会比java 6 的实现要差一些。这种实现显得有点“糟糕”。
问题的价值
虽然这个问题出现在Java 6并且Java 7中已经修复,但并不代表我们就不需要了解,况且Java 7的重新实现被喷的很厉害。
其实这个问题的价值,还是比较宝贵的,尤其是内容字符数组共享这个优化的实现。希望可以为大家以后的设计实现提供帮助和一些想法。
受影响的方法
trim和subSequence都存在调用substring的操作。Java 6和Java 7 substring实现的更改也间接影响到了这些方法。
以下三篇文章写得都比较不错,但是都稍微有一些问题,我都已经标明出来,大家阅读时,需要注意。
本文中解决java6中问题提到的字符串拼接不推荐,具体原因可以参考
本文中提到的有一个概念错误,新的字符串不会阻止旧的字符串被回收,而是阻止旧字符串中的内容字符数组。阅读时需要注意。
本文中提到的有一个测试,使用非new的形式有一点问题,其忽视了字符串常量池的存在,具体查看下面的注意。
上面的重现问题的代码中
String getString() {
//return this.largeString.substring(0,2);
return new String(&ab&);
这里最好不要写成下面这样,因为在JVM中存在字符串常量池,”ab”不会重新创建新字符串,所有的变量都会引用一个对象,而使用new String()则每次重新创建对象。
String getString() {
return &ab&;
关于字符串常量池,以后的文章会有介绍。
如果你对本文这样的内容感兴趣,可以阅读以下Joshua Bloch大神写得书,虽然有点贵,还是英文的。
这篇文章希望不要对新手产生误导,作者只是讲java中“没有可以让产生感觉存在值类型”的特性,但不代表...
关于ImportNew
ImportNew 专注于 Java 技术分享。于日 11:11正式上线。是的,这是一个很特别的时刻 :)
ImportNew 由两个 Java 关键字 import 和 new 组成,意指:Java 开发者学习新知识的网站。 import 可认为是学习和吸收, new 则可认为是新知识、新技术圈子和新朋友……
新浪微博:
推荐微信号
反馈建议:@
广告与商务合作QQ:
– 好的话题、有启发的回复、值得信赖的圈子
– 写了文章?看干货?去头条!
– 为IT单身男女服务的征婚传播平台
– 优秀的工具资源导航
– 活跃 & 专业的翻译小组
– 国内外的精选博客文章
– UI,网页,交互和用户体验
– JavaScript, HTML5, CSS
– 专注Android技术分享
– 专注iOS技术分享
– 专注Java技术分享
– 专注Python技术分享
& 2017 ImportNewjava(21)
在Java中开发,String是我们开发程序可以说必须要使用的类型,String有一个substring方法用来截取字符串,我们想必也常常使用。但是你知道么,关于Java 6中的substring是否会引起内存泄露,在国外的论坛和社区有着一些讨论,以至于Java官方已经将其标记成bug,并且为此Java 7 还重新进行了实现。读到这里可能你的问题就来了,substring怎么会引起内存泄露呢?那么我们就带着问题,走进小黑屋,看看substring有没有内存泄露,又是怎么导致所谓的内存泄露。
substring方法提供两种重载,第一种为只接受开始截取位置一个参数的方法。
public&String&substring(int&beginIndex)
比如我们使用上面的方法,”unhappy”.substring(2)&返回结果&”happy”
另一种重载就是接受一个开始截取位置和一个结束截取位置的参数的方法。
public&String&substring(int&beginIndex,&int&endIndex)
使用这个方法,”smiles”.substring(1, 5)&返回结果&”mile”
通过这个介绍我们基本了解了substring的作用,这样便于我们理解下面的内容。
因为这个问题出现的情况在Java 6,如果你的Java版本号不是Java 6 需要调整一下。
终端调整(适用于Mac系统)
查看java版本号
13:03&$&java&-version
java&version&&1.8.0_25&
Java(TM)&SE&Runtime&Environment&(build&1.8.0_25-b17)
Java&HotSpot(TM)&64-Bit&Server&VM&(build&25.25-b02,&mixed&mode)
export&JAVA_HOME=$(/usr/libexec/java_home&-v&1.6)
Ubuntu使用alternatives –config java,Fedora上面使用alternatives –config java。
如果你使用Eclipse,可以选择工程,右击,选择Properties(属性)— Java Compiler(Java编译器)进行特殊指定。
这里贴一下java官方bug里用到的重现问题的代码。
public&class&TestGC&{
& & private&String&largeString&=&new&String(new&byte[100000]);
& & String&getString()&{
& & & & return&this.largeString.substring(0,2);
& & public&static&void&main(String[]&args)&{
& & & & java.util.ArrayList&list&=&new&java.util.ArrayList();
& & & & for&(int&i&=&0;&i&&&1000000;&i++)&{
& & & & & & TestGC&gc&=&new&TestGC();
& & & & & & list.add(gc.getString());
然而上面的代码,只要使用Java 6 (Java 7和8 都不会抛出异常)运行一下就会报java.lang.OutOfMemoryError: Java heap space的异常,这说明没有足够的堆内存供我们创建对象,JVM选择了抛出异常操作。
于是有人会说,是因为你每个循环中创建了一个TestGC对象,虽然我们加入ArrayList只是两个字符的字符串,但是这个对象中又存储largeString这么大的对象,这样必然会造成OOM的。
然而,其实你说的不对。比如我们看一下这样的代码,我们只修改getString方法。
public&class&TestGC&{
& & private&String&largeString&=&new&String(new&byte[100000]);
& & String&getString()&{
& & & & //return&this.largeString.substring(0,2);
& & & return&new&String(&ab&);
& & public&static&void&main(String[]&args)&{
& & & & java.util.ArrayList&list&=&new&java.util.ArrayList();
& & & & for&(int&i&=&0;&i&&&1000000;&i++)&{
& & & & & & TestGC&gc&=&new&TestGC();
& & & & & & list.add(gc.getString());
执行上面的方法,并不会导致OOM异常,因为我们持有的时1000000个ab字符串对象,而TestGC对象(包括其中的largeString)会在java的垃圾回收中释放掉。所以这里不会存在内存溢出。
那么究竟是什么导致的内存泄露呢?要研究这个问题,我们需要看一下方法的实现,即可。
深入Java 6实现
在String类中存在这样三个属性
value 字符数组,存储字符串实际的内容offset 该字符串在字符数组value中的起始位置count 字符串包含的字符的长度
Java 6中substring的实现
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);
上述方法调用的构造方法
//Package&private&constructor&which&shares&value&array&for&speed.
String(int&offset,&int&count,&char&value[])&{
& this.value&=&
& this.offset&=&
& this.count&=&
当我们读完上述的代码,我们应该会豁然开朗,原来是这个样子啊!
当我们调用字符串a的substring得到字符串b,其实这个操作,无非就是调整了一下b的offset和count,用到的内容还是a之前的value字符数组,并没有重新创建新的专属于b的内容字符数组。
举个和上面重现代码相关的例子,比如我们有一个1G的字符串a,我们使用substring(0,2)得到了一个只有两个字符的字符串b,如果b的生命周期要长于a或者手动设置a为null,当垃圾回收进行后,a被回收掉,b没有回收掉,那么这1G的内存占用依旧存在,因为b持有这1G大小的字符数组的引用。
看到这里,大家应该可以明白上面的代码为什么出现内存溢出了。
共享内容字符数组
其实substring中生成的字符串与原字符串共享内容数组是一个很棒的设计,这样避免了每次进行substring重新进行字符数组复制。正如其文档说明的,共享内容字符数组为了就是速度。但是对于本例中的问题,共享内容字符数组显得有点蹩脚。
对于之前比较不常见的1G字符串只截取2个字符的情况可以使用下面的代码,这样的话,就不会持有1G字符串的内容数组引用了。
String&littleString&=&new&String(largeString.substring(0,2));
下面的这个构造方法,在源字符串内容数组长度大于字符串长度时,进行数组复制,新的字符串会创建一个只包含源字符串内容的字符数组。
public&String(String&original)&{
& int&size&=&original.
& char[]&originalValue&=&original.
& 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.
& & & 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&=&originalV
& this.offset&=&0;
& this.count&=&
& this.value&=&v;
Java 7 实现
在Java 7 中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&-&beginI
& & if&(subLen&&&0)&{
& & & throw&new&StringIndexOutOfBoundsException(subLen);
& & return&((beginIndex&==&0)&&&&(endIndex&==&value.length))&?&this
& & & & & & & & :&new&String(value,&beginIndex,&subLen);
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);
真的是内存泄露么
我们知道了substring某些情况下可能引起内存问题,但是这个叫做内存泄露么?
其实个人认为这个不应该算为内存泄露,使用substring生成的字符串b固然会持有原有字符串a的内容数组引用,但是当a和b都被回收之后,该字符数组的内容也是可以被垃圾回收掉的。
哪个版本实现的好
关于Java 7 对substring做的修改,收到了褒贬不一的反馈。
个人更加倾向于Java 6的实现,当进行substring时,使用共享内容字符数组,速度会更快,不用重新申请内存。虽然有可能出现本文中的内存性能问题,但也是有方法可以解决的。
Java 7的实现不需要程序员特殊操作避免了本文中问题,但是进行每次substring的操作性能总会比java 6 的实现要差一些。这种实现显得有点“糟糕”。
问题的价值
虽然这个问题出现在Java 6并且Java 7中已经修复,但并不代表我们就不需要了解,况且Java 7的重新实现被喷的很厉害。
其实这个问题的价值,还是比较宝贵的,尤其是内容字符数组共享这个优化的实现。希望可以为大家以后的设计实现提供帮助和一些想法。
受影响的方法
trim和subSequence都存在调用substring的操作。Java 6和Java 7 substring实现的更改也间接影响到了这些方法。
以下三篇文章写得都比较不错,但是都稍微有一些问题,我都已经标明出来,大家阅读时,需要注意。
&本文中解决java6中问题提到的字符串拼接不推荐,具体原因可以参考&本文中提到的有一个概念错误,新的字符串不会阻止旧的字符串被回收,而是阻止旧字符串中的内容字符数组。阅读时需要注意。&本文中提到的有一个测试,使用非new的形式有一点问题,其忽视了字符串常量池的存在,具体查看下面的注意。
上面的重现问题的代码中
String&getString()&{
& //return&this.largeString.substring(0,2);
& & & return&new&String(&ab&);
这里最好不要写成下面这样,因为在中存在字符串常量池,”ab”不会重新创建新字符串,所有的变量都会引用一个对象,而使用new String()则每次重新创建对象。
String&getString()&{
& & & return&&ab&;
关于字符串常量池,以后的文章会有介绍。
另外,这篇文章对的10大问题进行了详细的介绍,有兴趣的朋友也可以看看。
原文地址:
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:17602次
排名:千里之外
原创:25篇
转载:23篇
(1)(1)(3)(1)(2)(1)(1)(3)(2)(9)(3)(15)(7)(1)

我要回帖

更多关于 java substring的用法 的文章

 

随机推荐