记一次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的培 ...
随机推荐
- Linux(一)——认识Linux
一.Linux介绍 (安装的是Centos6.7) 1.Linux 系统是一套免费使用和自由传播的类 Unix 操作系统(主要用在服务器上),是一个基于 POSIX 和 UNIX 的多用户.多任务.支 ...
- [POJ3613] Cow Relays
link 题目大意 给你一个含有边权的无向图,问从$S$到$T$经过$N$条边的最小花费. 试题分析 我们可以很容易推导$dp$方程,$dp(k,i,j)$表示经过$k$条边从$i$到$j$的最小花费 ...
- 【CF375C】Circling Round Treasures
Portal --> CF375C Solution 一个有趣的事情:题目中有很大的篇幅在介绍如何判断一个位置在不在所围的多边形中 那么..给了方法当然就是要用啊 首先是不能包含\('B'\ ...
- python基础---- __getattribute__----__str__,__repr__,__format__----__doc__----__module__和__class__
目录: 一. __getattribute__ 二.__str__,__repr__,__format__ 三.__doc__ 四.__module__和__class__ 一. __getattri ...
- 在 Xamarin.Forms 实现密码输入EntryCell
在 Xamarin.Forms 中,我们通常使用 TableView 来构建输入表单.Xamarin 为我们提供了 EntryCell 用于输入文本,但是其并不支持密码输入,即密码掩码.这里要对 En ...
- springboot缓存开发
前言:缓存在开发中是一个必不可少的优化点,近期在公司的项目重构中,关于缓存优化了很多点,比如在加载一些数据比较多的场景中,会大量使用缓存机制提高接口响应速度,简介提升用户体验.关于缓存,很多人对它都是 ...
- uC/OS-II之入门与介绍20160525
说一下刚学习uCOS的心得1)首先强调一下实时操作系统(RTOS)的特点,最明显的是提供及时响应和高可靠性2)基于实施操作系统的应用程序设计中,其中很重要的一个概念是"任务",任务 ...
- 【题解】彩色树 51nod 1868 虚树 树上dp
Prelude 题目在这里:ο(=•ω<=)ρ⌒☆ Solution 蒟蒻__stdcall的第一道虚树题qaq. 首先很容易发现,这个排列是假的. 我们只需要求出每对点之间的颜色数量,然后求个 ...
- nginx 前后分离,地址重写,url匹配中遇到的问题
我遇到的问题: 前端用vue的路由做页面路由,后台用spring mvc做数据接口,但是遇到路由地址和接口地址无法区分的问题,导致nginx无法正确准发比如: 1)http://127.0.0.1/i ...
- 前端PHP入门-025-数组-重中之重
数组是PHP中一个 很很很很很很很很很很重要 的一个 数据类型 . 学习数组,大家主要学习两部份的知识: 1.数组的定义,定义中的一些注意的坑 2.数组的函数使用 认识数组 数组定义 数组在之前我们让 ...