记一次java内存溢出的解决过程
注:本文主要记录这次解决内存溢出问题的过程而不是具体问题。
最近在写一个搜索引擎,使用倒排索引结构进行文档检索,保存索引的基本思想是先将倒排列表保存到内存中一个有序Map里(TreeMap),然后当内存占用达到一定阈值的时候将内存中的倒排列表有序写入磁盘,当磁盘已经存在索引时,则将内存中的索引和磁盘中的索引进行合并,生成新的索引,合并过程类似于归并排序。合并内存索引和磁盘索引的代码如下:
public synchronized void merge(){
LogUtil.info("InvertIndex merge start...");
File f=new File(path); //这个文件是原磁盘上的索引文件
//磁盘存在索引,合并磁盘索引和内存索引
if (f.exists()) {
String outPath=path+".temp";
File outFile=new File(outPath); TreeMap<String, TreeSet<Long>> ramSnapshot=null;
ramSnapshot=ram; //ram保存的是内存索引,这里因为ram可能被其他添加文档的线程修改,因此先存一份快照然后将ram清空,后面实际操作的是快照
ram=new TreeMap<>(); BufferedReader reader=null;
PrintWriter writer=null;
try {
//合并过程,有3个指针:分别指向内存索引的当前位置、原磁盘索引读到的位置、新的磁盘索引写入的位置
Iterator<Entry<String, TreeSet<Long>>> ramIterator= ramSnapshot.entrySet().iterator();
reader=new BufferedReader(new FileReader(f));
writer=new PrintWriter(new BufferedWriter(new FileWriter(outFile))); Entry<String, TreeSet<Long>> entry=ramIterator.hasNext()?ramIterator.next():null;
String line=reader.readLine(); while (entry!=null&&line!=null) {
long freeRam=Runtime.getRuntime().freeMemory()/1000/1000;
System.out.println("freeRam: "+freeRam); String ramWord=entry.getKey();
String diskWord=line.split(separator1)[0];
String out=""; int c=ramWord.compareTo(diskWord);
//合并过程,因为是两个有序列表,采用类似归并排序的合并方法,区别在于这里如果遇到倒排词相等的时候,需要合并到一个倒排词(合并两者文档列表)
if (c==0) {
TreeSet<Long> ramDocIds=entry.getValue();
TreeSet<Long> diskDocIds=this.convertLine2DocIds(line);
TreeSet<Long> union=ramDocIds;
union.addAll(diskDocIds);
out=this.convertIndex2Line(ramWord, union); entry=ramIterator.hasNext()?ramIterator.next():null;
line=reader.readLine();
}else if (c<0) {
out=this.convertIndex2Line(ramWord, entry.getValue());
entry=ramIterator.hasNext()?ramIterator.next():null;
}else {
out=this.convertIndex2Line(diskWord, this.convertLine2DocIds(line));
line=reader.readLine();
} writer.println(out);
}
LogUtil.info("InvertIndex complex merge complete."); while (ramIterator.hasNext()) {
entry=ramIterator.next();
String out=this.convertIndex2Line(entry.getKey(), entry.getValue());
writer.println(out);
}
LogUtil.info("InvertIndex ram merge complete."); while ((line=reader.readLine())!=null) {
writer.println(line);
}
LogUtil.info("InvertIndex disk merge complete."); } catch (Exception e) {
LogUtil.err("merge ram index and disk index fail.", e);
}finally {
try {
if (reader!=null) {
reader.close();
}
if (writer!=null) {
writer.close();
}
} catch (Exception e2) {LogUtil.err("release resource fail.", e2);}
}
f.delete();
if (!outFile.renameTo(new File(path))) {
throw new RuntimeException("rename temp file fail.");
}
}else {
//磁盘上原本不存在索引,直接将内存索引写入磁盘
//代码略
}
代码的主要思想是维持3个指针:内存索引是一个有序TreeMap,iterator相当于一个虚拟指针;磁盘索引也是有序的,BufferdReader相当于一个虚拟指针;合并后生成的新索引使用BufferdWriter作为指针。然后使用归并排序的思想,比较内存索引词和磁盘索引词的大小,哪个小就将哪个写入新的索引然后将其指针前进一位,如果两个索引词相等,则合并两者的文档列表。
从上面的描述来看以上代码使用的内存应该是O(1)的,因为内存中除了内存索引之外,同一时刻只会从磁盘索引读出一行,但是实际运行的时候,总是在合并时报出GC overhead limit exceeded,这个异常就是说jvm用了大量时间(超过98%)执行GC但是只释放了很少的堆内存(小于2%),换句话说就是OOM的前兆。根据我对程序的内存占用的分析,这种情况是不正常的。
于是给程序添加 “-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=C:/dump” 参数,在程序出现内存溢出异常的时候dump出内存信息。
用eclipse MAT查看占用内存最多的对象:
发现是TreeMap的实例,在这个程序中我用了TreeMap来保存内存索引,而在合并索引的过程中,内存索引的大小应该是不变的,那么为什么会溢出呢?
通过看代码将问题定位在了红色代码处:
这里首先将ramDocIds指向entry.getValue(),然后又将union指向ramDocIds,此时union实际上是直接指向entry.getValue()的,然后union执行了addAll操作。。众所周知,addAll操作是将元素加到对象本身的,这里我的原意是声明一个局部变量保存列表合并结果然后存入新的索引文件,但是无意中却同时修改了内存索引导致内存索引越来越大。
OK,到这里问题就解决了,只需要将
TreeSet<Long> union=ramDocIds;
改为
TreeSet<Long> union=new TreeSet<>(ramDocIds);
即可。
回顾一下这次解决内存溢出问题的过程:
- 分析程序空间复杂度,看内存溢出的是否正常。
- 添加jvm参数,让程序在内存溢出的时候dump出内存快照。
- 使用eclipse MAT分析占用内存最多的对象。
- 在源码中找到相应的对象查找问题。
记一次java内存溢出的解决过程的更多相关文章
- java内存溢出的解决思路
原文地址:https://www.cnblogs.com/200911/p/3965108.html 内存溢出是指应用系统中存在无法回收的内存或使用的内存过多,最终使得程序运行要用到的内存大于虚拟机能 ...
- java内存溢出怎么解决
java.lang.OutOfMemoryError这个错误我相信大部分开发人员都有遇到过,产生该错误的原因大都出于以下原因:JVM内存过小.程序不严密,产生了过多的垃圾. 导致OutOfMemory ...
- 添加IFrame导致内存溢出的解决过程(IE浏览器,目前发现了原因,还未解决)
1. 现象 每次动态添加iframe时,iexplore.exe进程占据的内存都会增加(大概10M左右),不会自动释放,最终导致内存溢出 2. 解决过程 经过网络的一番搜索,基本上给出的解决方案是 ...
- Java 内存溢出(java.lang.OutOfMemoryError)的常见情况和处理方式总结
最近老是遇见服务器内存溢出的问题,故在网上搜了搜,总结了一些java内存溢出的解决方式 java.lang.OutOfMemoryError这个错误我相信大部分开发人员都有遇到过,产生该错误的原因大都 ...
- Java常见的几种内存溢出及解决方法
Java常见的几种内存溢出及解决方法[情况一]:java.lang.OutOfMemoryError:Javaheapspace:这种是java堆内存不够,一个原因是真不够(如递归的层数太多等),另一 ...
- android通过BitmapFactory.decodeFile获取图片bitmap报内存溢出的解决办法
android通过BitmapFactory.decodeFile获取图片bitmap报内存溢出的解决办法 原方法: public static Bitmap getSmallBitmap(Strin ...
- Java内存溢出详解
转自:http://elf8848.iteye.com/blog/378805 一.常见的Java内存溢出有以下三种: 1. java.lang.OutOfMemoryError: Java heap ...
- Java内存溢出的详细解决方案
本文介绍了Java内存溢出的详细解决方案.本文总结内存溢出主要有两种情况,而JVM经常调用垃圾回收器解决内存堆不足的问题,但是有时仍会有内存不足的错误.作者分析了JVM内存区域组成及JVM设置虚拟内存 ...
- 老李案例分享:定位JAVA内存溢出
老李案例分享:定位JAVA内存溢出 poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.在poptest的loadrunner的培 ...
随机推荐
- YOLO v1之总结篇(linux+windows)
YOLO出自2016 CVPR You Only Look Once:Unified, Real-Time Object Detection,也是一个非常值得学习的框架,不得不说facebook的技术 ...
- Mybatis中jdbcType和javaType对应关系
Mybatis中javaType和jdbcType对应关系 JDBC Type Java Type CHAR String VARCHAR ...
- php 修改图片分辨率
<?php function resize_image($file, $w, $h, $crop=FALSE) { list($width, $height) = getimagesize($f ...
- Adreno GPU Profiler工具使用总结
Adreno Profiler介绍 Adreno Profiler 是高通公司开发的一款针对运行在高通骁龙处理器上用于图形和GPGPU技术应用的性能分析和帧调试工具.工具本质上是一个OpenGL ES ...
- git clone 指定分支的内容
使用Git下载指定分支命令为:git clone -b 分支名仓库地址 使用Git下载v.2.8.1分支代码,使用命令:git clone -b v2.8.1 https://git.oschina. ...
- 「HTML5」url、href、src区别
一.URL的概念 统一资源定位符(或称统一资源定位器/定位地址.URL地址等,英语:Uniform Resource Locator,常缩写为URL),有时也被俗称为网页地址(网址).如同在网络上的门 ...
- js 30分钟倒计时
<html> <head> <meta charset="UTF-8"> <title></title> </he ...
- nodejs使用场景
NodeJS的工作原理其实就是事件循环.可以说每一条NodeJS的逻辑都是写在回调函数里面的,而回调函数都是有返回之后才异步执行的! 既然NodeJS处理并发的能力强,但处理计算和逻辑的能力反而很弱, ...
- 解决VS Code编译调试中文输出乱码
最近尝试用VS Code配置了C和C++的编译调试环境,结果遇到了中文输出乱码问题,查阅网上竟然还没有相关问题,有怀疑是mingw中文支持问题,但最后证明是VS Code编码问题. 解决方案: 文件- ...
- stat命令--文件权限属性设置
stat命令文件权限属性设置 stat命令用于显示文件的状态信息. stat命令的输出信息比ls命令的输出信息要更详细. 语法 stat(选项)(参数) 选项 -L:支持符号连接: -f:显示文件系统 ...