首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 其他教程 > 开源软件 >

哪位高手喂饱了你的内存

2013-10-27 
谁喂饱了你的内存?最近同事在做大数据量操作的时候,在一个明显不应该出现内存溢出的地方,报出了OutOfMemor

谁喂饱了你的内存

?最近同事在做大数据量操作的时候,在一个明显不应该出现内存溢出的地方,报出了OutOfMemoryError?,经过分析终于找到了原因。本文中,将模拟当时的情景,重现并分析出这个问题。

先看实例代码,2个类:

package?test.bo;

?

public?class?Comment?{

private?String?title;

private?String?comment;

?

public?String?getTitle()?{

return?title;

}

?

public?void?setTitle(String?title)?{

this.title?=?title;

}

?

public?String?getComment()?{

return?comment;

}

?

public?void?setComment(String?comment)?{

this.comment?=?comment;

}

}

Comment,一个简单的PO,有两个字符串的属性。

?

?

package?test.bo;

?

import?java.util.ArrayList;

import?java.util.List;

import?java.util.Random;

?

public?class?Main?{

List<Comment>?comments?=?new?ArrayList<Comment>();

?

public?static?void?main(String[]?args)?{

Main?mainClass?=?new?Main();

for?(int?i?=?0;?;?i++)?{

Comment?comment?=?new?Comment();

comment.setTitle("Title"+i);

comment.setComment(generateComment(4000).substring(0,?20));

mainClass.addComment(comment);

}

?

}

?

private?void?addComment(Comment?comment){

this.comments.add(comment);

}

?

private?static?String?generateComment(int?length){

Random?randGen?=?new?Random();??

char[]?numbersAndLetters??=?("abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ").toCharArray();?

?char?[]?randBuffer?=?new?char[length];

?for?(int?i=0;?i<randBuffer.length;?i++)?{??

?randBuffer[i]?=?numbersAndLetters[randGen.nextInt(20)];

?}

?return?new?String(randBuffer);

}

}

?

Main类中,去做了一件事:往实例变量comments中不断地插入数据,直到程序内存溢出,至于generateComment方法,只是为了每次构造的comment字符串是不同的。

?

在运行程序之前,先给虚拟机设置两个限制参数,并且在内存溢出的时候,生成内存快照:

-XX:+HeapDumpOn

-Xms20m

-Xmx20m

?

运行程序,接着就会出现OutOfMemoryError。

?

用Eclipse?Memory?Analyzer打开内存快照,如图:

哪位高手喂饱了你的内存

?

对象comments,占用了大部分的内存,这是列表里面的Comment对象占用了内存,

随便点开一个查看其内容,如下图:

哪位高手喂饱了你的内存

?

其中,每一个Comment对象占用的内存大小都是一样的,8144,对象中只有两个字符串,一个是comment,一个是title,总共一起三十个字符左右,怎么会占用这么大的内存?

?哪位高手喂饱了你的内存

?

?

看看comment里面是什么值:

哪位高手喂饱了你的内存

?

继续看里面的值:

哪位高手喂饱了你的内存

?

这个,不就是我们当时创建的随机字符串么?

?

为什么会出现这样的情况?

?

?

按照上面的分析,应该是value这个属性引用了那个长度为4000的字符串。

看String的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);

????}

?

最后,调用了这一句:

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

?

这是一个什么构造方法?

看看它的实现:

????String(int?offset,?int?count,?char?value[])?{

this.value?=?value;

this.offset?=?offset;

this.count?=?count;

????}

?

我们的随机字符串是通过如下的构造方法来的:

public?String(char?value[])?{

this.offset?=?0;

this.count?=?value.length;

this.value?=?StringValue.from(value);

????}

?

也就是说,在构造这个随机字符串的时候,就把这个4000长度的char数组赋值给随机字符串的value属性了。

而在我们substring的时候,又顺便将value赋值给了新生成的String的value属性,也就是说:

substring方法过后,新生成的字符串,保留了对原来字符串的一个备份。

这样一来,内存中存在了很多不需要的字符串不能被GC掉,

是这些喂饱了你的内存。

?

?

果真是这样的吗?

如果是这样,我们在最开始的时候,就只传入20个长度的字符串,就应该不会有8144长度的字符串,修改一下代码:

......

public?static?void?main(String[]?args)?{

Main?mainClass?=?new?Main();

for?(int?i?=?0;?i?<?1000000;?i++)?{

Comment?comment?=?new?Comment();

comment.setTitle("Title"+i);

comment.setComment(generateComment(20));

mainClass.addComment(comment);

}

}

......

?

这样产生溢出之后,再来分析它的内存快照:

哪位高手喂饱了你的内存

?

只有184了,明显小了,查看每个Comment对象的comment属性,也正常了,如图:

哪位高手喂饱了你的内存

?

?

看来,还真的是substring这个方法的问题,这种情况该如何规避呢?

修改代码:

?

......

public?static?void?main(String[]?args)?{

Main?mainClass?=?new?Main();

for?(int?i?=?0;?i?<?1000000;?i++)?{

Comment?comment?=?new?Comment();

comment.setTitle("Title"+i);

comment.setComment(

new?String(generateComment(4000).substring(0,?20)));

mainClass.addComment(comment);

}

}

......

?

再看溢出快照:

哪位高手喂饱了你的内存?

?

已经和没有substring的情况一样了。

<!--EndFragment-->

热点排行