最近同事在做大数据量操作的时候,在一个明显不应该出现内存溢出的地方,报出了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-->
相关推荐
运行某些程序的时候,有时会出现内存错误的提示,然后该程序就关闭。 “0x????????”指令引用的“0x?...”内存。该内存不能为“read”。...不知你出现过类似这样的故障吗?(0x后面内容有可能不一样。)
通过易于使用的界面来创建针对每个植物量身定制的植物浇水时间表, Water My Plants将提醒用户何时该喂饱叶子并缓解植物的口渴。 :check_mark_button: 最有价值球员 user可以通过提供唯一的username ,有效的手机...
Quizapp1 试图为我的测验应用程序的主题寻求一些与众不同的想法,我最终花光了时间把所有东西都投入进去并像应该的那样工作....最终只是喂饱了我自己的冒名顶替综合症,哈哈.. ..希望它能帮助某个人!
window喂狗程序,winio控制,文章地址:https://mp.csdn.net/postedit/82968744
标题、描述和标签全部由"喂喂喂"组成,并没有提供任何实质性的内容。而压缩包子文件的文件名称列表只给出了"新建文件夹",这同样无法推断出具体的IT知识主题。如果您能提供关于编程语言、操作系统、网络技术、数据库...
非常可爱可以喂鱼玩鱼的屏保程序。可以设置小鱼儿的种类和数量,可以喂饲料给鱼吃,玩它,很有意思。
51单片机按键喂狗程序,程序简单易懂,是很不错的一个样例。
在"大鱼喂小鱼"游戏中,Canvas可能被用来绘制游戏场景、角色和动态效果,例如鱼儿游动、食物飘浮等。 JavaScript是实现游戏交互的关键,它负责处理用户的输入,如鼠标点击或触摸屏幕,以及游戏内部的逻辑,如鱼儿的...
逗鱼、喂鱼的热带鱼屏保逗鱼、喂鱼的热逗鱼、喂鱼的热带鱼屏保带鱼屏保逗鱼、喂鱼的热带鱼屏保
74-按键喂狗(51单片机C语言实例Proteus仿真和代码)74-按键喂狗(51单片机C语言实例Proteus仿真和代码)74-按键喂狗(51单片机C语言实例Proteus仿真和代码)74-按键喂狗(51单片机C语言实例Proteus仿真和代码)74-按键喂狗...
介绍了51 系列单片机在实践中通常使用的单片机外接看门狗的两硬件喂狗方式和软件喂狗方式 ,并对两种方式的适用范围和可靠性进行了详细的分析和比较。
本文讨论了在基于Linux的嵌入式系统中实现全程喂狗策略的方法。喂狗策略是指在嵌入式系统中引入看门狗电路,以确保系统在出现异常时能够自动重启,从而提高系统的稳定性和可靠性。看门狗电路本质上是一个计数器,它...
单片机C语言实例--74-按键喂狗.zip
51单片机教程实例74-按键喂狗
标题中的“3D动画桌面 可以自己设置 喂鱼”表明这是一款具有三维动画效果的桌面软件,用户可以自定义设置,其中包括一个有趣的喂鱼功能。这意味着该软件不仅提供了一个动态的桌面背景,还允许用户进行个性化操作,...
【喂兔子游戏java练习项目源码】是一个适合初学者或者想要巩固Java编程基础的开发者进行实践的项目。这个游戏的核心在于通过控制台交互实现喂养兔子的过程,锻炼了开发者对Java语言的理解和应用能力,特别是面向对象...
C51看门狗喂狗操作.附带C51源码和ISIS图纸.
### 详解多任务看门狗及喂狗方法 #### 看门狗基本概念 看门狗技术在嵌入式系统开发中极为重要,主要用于监控系统运行状态,并在系统异常时进行复位处理,从而提高系统的可靠性和稳定性。看门狗可以分为硬件看门狗...