`
Irving_wei
  • 浏览: 132568 次
  • 性别: Icon_minigender_1
  • 来自: Heaven
社区版块
存档分类
最新评论

谁喂饱了你的内存

    博客分类:
  • JVM
 
阅读更多

 最近同事在做大数据量操作的时候,在一个明显不应该出现内存溢出的地方,报出了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的字符串。

Stringsubstring方法的实现:

 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赋值给了新生成的Stringvalue属性,也就是说:

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-->
  • 大小: 60.3 KB
  • 大小: 65.6 KB
  • 大小: 41 KB
  • 大小: 40.8 KB
  • 大小: 41 KB
  • 大小: 94.6 KB
  • 大小: 38.2 KB
  • 大小: 76.9 KB
  • 大小: 94.6 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics