巨大的LinkedList导致GC开销限制,有另一种解决方案?(Huge LinkedList is

2019-09-21 18:57发布

这里是我的代码:

 public void mapTrace(String Path) throws FileNotFoundException, IOException {
    FileReader arq = new FileReader(new File(Path));
    BufferedReader leitor = new BufferedReader(arq, 41943040);
    Integer page;
    String std;
    Integer position = 0;

    while ((std = leitor.readLine()) != null) {
        position++;
        page = Integer.parseInt(std, 16);
        LinkedList<Integer> values = map.get(page);
        if (values == null) {
            values = new LinkedList<>();
            map.put(page, values);
        }
        values.add(position);
    }

    for (LinkedList<Integer> referenceList : map.values()) { 
        Collections.reverse(referenceList); 
    }

}

这是HashMap的结构

       Map<Integer, LinkedList<Integer>> map = new HashMap<>();

对于50MB - 100MB跟踪文件我没有任何问题,但更大的文件,我有:

Exception in thread "AWT-EventQueue-0" java.lang.OutOfMemoryError: GC overhead limit exceeded

,或者我不知道如果反向方法增加了内存使用,如果LinkedList的使用比其他目录结构更多的空间,如果比它应该我添加列表到地图的方式正在采取更多的空间。 有谁可以告诉我这是用这么大的空间?

Answer 1:

有谁可以告诉我这是用这么大的空间?

简短的回答是,这可能是正在使用的空间,你所选择的数据结构的空间开销。

  1. 据我估计,一个LinkedList<Integer>在64位JVM使用大约48字节每整数存储的列表中包括的整数自己。

  2. 据我估计,一个Map<?, ?>在64位计算机上将在48个字节每个条目存储的不包括空间区域使用需要来表示键和值的对象。

现在,你的跟踪大小的估计是相当过于含糊,我要插入一些数字,但我期待的1.5Gb跟踪文件需要大量堆相比2GB的。


鉴于您所提供的数字,一个合理的原则进行的拇指是一个跟踪文件将占据堆内存约10倍,其文件大小......使用您当前使用的数据结构。

你不想配置JVM尝试使用更多的内存比可用的物理内存。 否则,你是负责向机器推入颠簸...和操作系统容易开始杀死进程。 因此,对于一个8GB的机器,我不会建议去在-Xmx8g。

把那在一起,8GB的机器,你应该能够应付600MB的跟踪文件(假设我的估计是正确的),但为1.5Gb跟踪文件是不可行的。 如果你真的需要处理跟踪文件那么大,我的建议是要么:

  • 设计和实施具体使用情况自定义集合类型更有效地使用内存,

  • 重新考虑你的算法,这样你就不需要在内存中保留整个跟踪文件,或

  • 获得更大的机器。


我读您的评论之前做了一些测试,我把-Xmx14g和处理600MB的文件,它采取了一些分钟(约10),但它确实不错。

-Xmx14g选项设置最大堆大小。 根据观察到的行为,我想到的是,JVM并不需要像这么大的内存的任何地方......,并没有从操作系统请求它。 如果你想看着在任务管理器的内存使用情况,我希望你会看到与一致的数字。

然后,我把-Xmx18g并试图处理1,5gb文件,其已运行约20分钟。 我在任务管理器内存会从7.80到7.90。 我不知道这是否会结束,我怎么会使用更多的内存比我有吗? 是否使用HD作为虚拟内存?

是的,这是它做什么。

是的,你的流程的虚拟地址空间的每个页面对应硬盘上的页面。

如果你有多个虚拟页面比物理内存页,在任何给定的时间一些这些虚拟内存页面将只生活在磁盘上。 当应用程序试图使用这些非居民页面的一个,虚拟机硬件产生中断,且操作系统发现未使用的页面并从光盘复制填充它,然后把控制权回到你的程序。 但是如果你的应用程序正忙,那么它将不得不被驱逐另一页物理内存页面。 这可能有参与编写驱逐页面中的内容刻录到光盘。

最终的结果是,当您尝试使用显著更多的虚拟地址的页面比你的物理内存,应用程序会产生大量的中断导致大量的磁盘读取和写入。 这就是所谓的颠簸。 如果您的系统捶打太严重,系统将花费其大部分的等待对光盘的读取和写入完成,并且性能会大幅度下降。 而在某些操作系统,该操作系统将尝试通过杀死进程“修复”的问题。



Answer 2:

继斯蒂芬相当合理的答案,任何事物都有其极限,你的代码根本就没有可扩展性 。

在输入为“大”(在你的情况下)的情况下,唯一合理的方法是基于流的方法,这一段时间(通常情况下)更为复杂的编写,使用很少的内存/资源。 基本上你在内存中保留您需要处理当前任务,然后尽快释放它只是什么。

您可能会发现UNIX命令行工具是你最好的武器,可能使用的组合awksedgrep等按摩你的原始数据转换成希望可用的“最终格式”。


我一旦停止同事从编写Java程序在读取和解析XML,并发出INSERT语句的数据库:我教他如何使用一系列管道命令,以生成可执行SQL,然后将管道直接进入数据库命令行工具。 大约过了30分钟,得到它的权利,但完成任务。 而该文件是巨大的,所以在Java它会需要SAC解析器和JDBC,这是不好玩。



Answer 3:

建立这个结构,我会把这些数据的键/值数据存储喜欢的BerkeleyDB的Java。

peusdo代码

putData(db,page,value)
 {
 Entry key=new Entry();
 Entry data=new Entry();
 List<Integer> L=new LinkedList<Integer>();;
 IntegerBinding.intToEntry(page,key);
 if(db.get(key,data)==OperationStatus.SUCCESS)
    {
    TupleInput t=new TupleInput(data);
    int n=t.readInt();

    for(i=0;i< n;++n) L.add(n);
    }

  L.add(value);
  TupleOutput out=new TupleOutput();
  out.writeInt(L.size());

  for(int v: L)  out.writeInt(v);
  data=new Entry(out.toByteArray());
  db.put(key,data);
 }


文章来源: Huge LinkedList is causing GC overhead limit, is there another solution?