3

这是我的代码:

 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 是否比其他 List 结构使用更多空间,或者我将列表添加到地图的方式是否占用了更多空间。有谁可以告诉我是什么占用了这么多空间?

4

3 回答 3

3

有谁可以告诉我是什么占用了这么多空间?

简短的回答是,使用空间的可能是您选择的数据结构的空间开销。

  1. 据我估计,LinkedList<Integer>64 位 JVM 上的 a 使用列表中每个整数大约 48 字节的存储空间,包括整数本身。

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

现在,您的跟踪大小估计对我来说太模糊了,无法插入数字,但我预计 1.5Gb 的跟踪文件需要超过 2Gb 的堆。


鉴于您提供的数字,一个合理的经验法则是跟踪文件将在堆内存中占用大约其文件大小的 10 倍......使用您当前使用的数据结构。

您不希望将 JVM 配置为尝试使用比可用物理 RAM 更多的内存。否则,您很可能将机器推入颠簸状态......并且操作系统可能会开始杀死进程。因此,对于 8Gb 机器,我不建议使用 -Xmx8g。

综上所述,使用 8Gb 的机器,您应该能够处理 600Mb 的跟踪文件(假设我的估计是正确的),但 1.5Gb 的跟踪文件是不可行的。如果您真的需要处理那么大的跟踪文件,我的建议是:

  • 为您的特定用例设计和实现自定义集合类型,以更有效地使用内存,

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

  • 买一台更大的机器。


在阅读您的评论之前,我做了一些测试,我输入了 -Xmx14g 并处理了 600mb 文件,花了几分钟(大约 10 分钟),但效果很好。

-Xmx14g选项设置最大堆大小。根据观察到的行为,我希望 JVM 不需要那么多内存......并且没有从操作系统请求它。如果您在任务管理器中查看了内存使用情况,我希望您会看到与此一致的数字。

然后我把 -Xmx18g 并尝试处理 1,5gb 文件,它已经运行了大约 20 分钟。我在任务管理器中的记忆是从 7,80 到 7,90。我想知道这是否会完成,我怎么能使用比我拥有的更多的内存?它使用HD作为虚拟内存吗?

是的,它就是这样做的。

是的,您的进程虚拟地址空间的每一页对应于硬盘上的一个页。

如果您拥有的虚拟页面多于物理内存页面,那么在任何给定时间,其中一些虚拟内存页面将仅存在于磁盘上。当您的应用程序尝试使用其中一个非常驻页面时,VM 硬件会生成一个中断,并且操作系统会找到一个未使用的页面并从磁盘副本中填充它,然后将控制权交还给您的程序。但是,如果您的应用程序很忙,那么它将不得不通过逐出另一个页面来创建该物理内存页面。这可能涉及将被驱逐页面的内容写入磁盘。

最终结果是,当您尝试使用比物理内存多得多的虚拟地址页时,应用程序会生成大量中断,从而导致大量磁盘读取和写入。这被称为颠簸。如果您的系统抖动得太厉害,系统将花费大部分时间等待磁盘读取和写入完成,并且性能将急剧下降。在某些操作系统上,操作系统会尝试通过终止进程来“修复”问题。

于 2012-08-28T02:12:30.793 回答
0

除了斯蒂芬相当合理的回答之外,一切都有其限制,您的代码根本不可扩展

如果输入“大”(如您的情况),唯一合理的方法是基于流的方法,虽然(通常)编写起来更复杂,但使用的内存/资源非常少。本质上,您只在内存中保存处理当前任务所需的内容,然后尽快释放它。

您可能会发现 unix 命令行工具是您最好的武器,也许使用 , 等的组合awksedgrep的原始数据处理成一个可用的“最终格式”。


我曾经阻止一位同事编写 java 程序来读取和解析 XML 并向数据库发出插入语句:我向他展示了如何使用一系列管道命令生成可执行 SQL,然后将其直接管道传递到数据库命令行工具. 花了大约 30 分钟才把它弄好,但工作完成了。而且文件很大,所以在 java 中它需要一个 SAC 解析器和 JDBC,这并不好玩。

于 2012-08-28T04:25:43.280 回答
0

为了构建这个结构,我会将这些数据放在一个键/值数据存储中,比如berkeleydb for java。

伪代码

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);
 }
于 2012-08-28T10:07:24.560 回答