注:本文主要记录这次解决内存溢出问题的过程而不是具体问题。

  最近在写一个搜索引擎,使用倒排索引结构进行文档检索,保存索引的基本思想是先将倒排列表保存到内存中一个有序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);

即可。

回顾一下这次解决内存溢出问题的过程:  

  1. 分析程序空间复杂度,看内存溢出的是否正常。
  2. 添加jvm参数,让程序在内存溢出的时候dump出内存快照。
  3. 使用eclipse MAT分析占用内存最多的对象。
  4. 在源码中找到相应的对象查找问题。

记一次java内存溢出的解决过程的更多相关文章

  1. java内存溢出的解决思路

    原文地址:https://www.cnblogs.com/200911/p/3965108.html 内存溢出是指应用系统中存在无法回收的内存或使用的内存过多,最终使得程序运行要用到的内存大于虚拟机能 ...

  2. java内存溢出怎么解决

    java.lang.OutOfMemoryError这个错误我相信大部分开发人员都有遇到过,产生该错误的原因大都出于以下原因:JVM内存过小.程序不严密,产生了过多的垃圾. 导致OutOfMemory ...

  3. 添加IFrame导致内存溢出的解决过程(IE浏览器,目前发现了原因,还未解决)

    1.  现象 每次动态添加iframe时,iexplore.exe进程占据的内存都会增加(大概10M左右),不会自动释放,最终导致内存溢出 2.  解决过程 经过网络的一番搜索,基本上给出的解决方案是 ...

  4. Java 内存溢出(java.lang.OutOfMemoryError)的常见情况和处理方式总结

    最近老是遇见服务器内存溢出的问题,故在网上搜了搜,总结了一些java内存溢出的解决方式 java.lang.OutOfMemoryError这个错误我相信大部分开发人员都有遇到过,产生该错误的原因大都 ...

  5. Java常见的几种内存溢出及解决方法

    Java常见的几种内存溢出及解决方法[情况一]:java.lang.OutOfMemoryError:Javaheapspace:这种是java堆内存不够,一个原因是真不够(如递归的层数太多等),另一 ...

  6. android通过BitmapFactory.decodeFile获取图片bitmap报内存溢出的解决办法

    android通过BitmapFactory.decodeFile获取图片bitmap报内存溢出的解决办法 原方法: public static Bitmap getSmallBitmap(Strin ...

  7. Java内存溢出详解

    转自:http://elf8848.iteye.com/blog/378805 一.常见的Java内存溢出有以下三种: 1. java.lang.OutOfMemoryError: Java heap ...

  8. Java内存溢出的详细解决方案

    本文介绍了Java内存溢出的详细解决方案.本文总结内存溢出主要有两种情况,而JVM经常调用垃圾回收器解决内存堆不足的问题,但是有时仍会有内存不足的错误.作者分析了JVM内存区域组成及JVM设置虚拟内存 ...

  9. 老李案例分享:定位JAVA内存溢出

    老李案例分享:定位JAVA内存溢出   poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.在poptest的loadrunner的培 ...

随机推荐

  1. 【BZOJ1176】Mokia(CDQ分治)

    [BZOJ1176]Mokia(CDQ分治) 题面 BZOJ权限题啊,,,, dbzoj真好 Description 维护一个W*W的矩阵,初始值均为S.每次操作可以增加某格子的权值,或询问某子矩阵的 ...

  2. CAS单点登录详细流程

    一.CAS简介和整体流程 CAS 是 Yale 大学发起的一个开源项目,旨在为 Web 应用系统提供一种可靠的单点登录方法,CAS 在 2004 年 12 月正式成为 JA-SIG 的一个项目.CAS ...

  3. (转载)Cobalt Strike tutorial下针对CVE-2017-0199利用

    CVE-2017-0199利用OLE对象嵌入Word / RTF文档的方式,使得可以在没有用户交互的情况下执行其内容.OLE由许多不同的程序支持,OLE通常用于使在另一个程序中可用的程序中创建的内容. ...

  4. Linux内核分析8

    周子轩 原创作品转载请注明出处  <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 实验目的: 使用gdb ...

  5. [转载]DataView详解

    表示用于排序.筛选.搜索.编辑和导航的 DataTable 的可绑定数据的自定义视图. DataView的功能类似于数据库的视图,他是数据源DataTable的封装对象,可以对数据源进行排序.搜索.过 ...

  6. NYOJ--520

    最大素因子 原题链接:http://acm.nyist.net/JudgeOnline/problem.php?pid=520 分析:先筛素数,同时记录下素数的序号,然后质因数分解. #include ...

  7. 多重检验_LSD方法不准确性

    sklearn实战-乳腺癌细胞数据挖掘(博客主亲自录制视频教程) https://study.163.com/course/introduction.htm?courseId=1005269003&a ...

  8. 源码包安装 NGINX时候遇到的错误以及解决办法!

    最近跟一个公司合作,要把我们的应用安装在他们的服务器上,不过问题来了.他们为了他们自己服务器安全,不给我们root权限,只给了我们普通用户权限,所有的程序都要装在规定的路径里,限制可不少.没办法装吧~ ...

  9. 【CODEVS】2800 送外卖

    [算法]最短路(floyd)+状态压缩型动态规划 [题解] 经典的TSP问题(货郎担问题):求最小权哈密顿回路(遍历全图点一次且仅一次).本题稍作改动,先说原TSP问题解法:状压DP. 状态用二进制表 ...

  10. POJ 1050 To the Max (最大子矩阵和)

    题目链接 题意:给定N*N的矩阵,求该矩阵中和最大的子矩阵的和. 题解:把二维转化成一维,算下就好了. #include <cstdio> #include <cstring> ...