获取Java OutOfMemoryError:我无法调试的Java堆空间错误

时间:2012-01-05 13:18:14

标签: java debugging stringbuilder out-of-memory

我正在努力弄清楚造成这种OutofMemory错误的原因。提供更多内存不是解决方案,因为我的系统没有足够的内存。相反,我必须找到一种重写代码的方法。

我已经简化了我的代码以尝试隔离错误。请看一下以下内容:

File[] files = new File(args[0]).listFiles();

int filecnt = 0;

LinkedList<String> urls = new LinkedList<String>();

for (File f : files) {
    if (filecnt > 10) {
        System.exit(1);
    }

    System.out.println("Doing File " + filecnt + " of " + files.length + " :" +                f.getName());

    filecnt++;
    FileReader inputStream = null;
    StringBuilder builder = new StringBuilder();

    try {
        inputStream = new FileReader(f);
        int c;
        char d;

        while ((c = inputStream.read()) != -1) {
            d = (char)c;
            builder.append(d);
        }
    }

    finally {
        if (inputStream != null) {
            inputStream.close();
        }
    }   

    inputStream.close();

    String mystring = builder.toString();
    String temp[] = mystring.split("\\|NEWandrewLINE\\|");

    for (String s : temp) {
        String temp2[] = s.split("\\|NEWandrewTAB\\|");
        if (temp2.length == 22) { 
            urls.add(temp2[7].trim());
        }
    }
}

我知道这段代码可能很混乱:)我在args [0]中指定的目录中有大量的文本文件。这些文本文件是由我创建的。我用了| NEWandrewLINE |表示文本文件中的新行,以及| NEWandrewTAB |表示新列。在此代码段中,我尝试访问每个存储行的URL(位于每行的第8列)。所以,我读了整个文本文件。字符串拆分| NEWandrewLINE |然后在| NEWandrewTAB |上的子字符串上再次进行字符串拆分。我使用以下行将URL添加到LinkedList(称为“urls”):urls.add(temp2 [7] .trim())

现在,运行此代码的输出是:

Doing File 0 of 973 :results1322453406319.txt
Doing File 1 of 973 :results1322464193519.txt
Doing File 2 of 973 :results1322337493419.txt
Doing File 3 of 973 :results1322347332053.txt
Doing File 4 of 973 :results1322330379488.txt
Doing File 5 of 973 :results1322369464720.txt
Doing File 6 of 973 :results1322379574296.txt
Doing File 7 of 973 :results1322346981999.txt
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:2882)
at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:100)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:572)
at java.lang.StringBuilder.append(StringBuilder.java:203)
at Twitter.main(Twitter.java:86)

主线86与线路构建器有关.append(d);在这个例子中。

但我不明白的是,如果我注释掉行urls.add(temp2 [7] .trim());我没有得到任何错误。所以错误似乎是由链表“urls”溢出造成的。但是为什么报告的错误与StringBuilder有关呢?

10 个答案:

答案 0 :(得分:4)

尝试将urls.add(temp2[7].trim());替换为urls.add(new String(temp2[7].trim()));

我认为你的问题是你实际上存储了整个文件内容,而不仅仅是你的网址列表中提取的URL字段,尽管这并不是很明显。它实际上是String类的特定于实现的问题,但通常String#split和String#trim返回新的String对象,它们包含与原始字符串相同的内部char数组,并且只有它们的offset和length字段不同。使用new String(String)构造函数可确保只保留原始数据的相关部分。

答案 1 :(得分:1)

每次添加字符串时链表都会使用更多内存。这意味着你可以留下没有足够的内存来构建你的StringBuilder。

避免此问题的方法是将结果写入文件而不是List,因为您似乎没有足够的内存来将List保留在内存中。

答案 2 :(得分:1)

因为这是

  1. 内存不足而且没有堆积
  2. 你有很多小型临时物品
  3. 我建议你给你的JVM一个适合你RAM的最大堆大小限制。

    要使用更少的内存,我会使用缓冲的阅读器来拉入整行,并节省临时对象的创建。

答案 3 :(得分:1)

简单的答案是:您不应该将文本文件中的所有URL加载到内存中。你肯定是这样做的,因为你想在下一步中处理它们。因此,不要将它们添加到内存中的List,而是执行下一步(可能存储在数据库中或检查它是否可访问)并忘记该URL。

答案 4 :(得分:1)

你有多少个URL?看起来你只是储存了比你能处理的更多的东西。

据我所知,链表是唯一没有限定在循环内的对象,因此无法收集。

对于OOM错误,抛出它的位置并不重要。

要正确检查,请使用分析器(查看JVisualVM以获取免费分析器,您可能已经拥有它)。您将看到堆中有哪些对象。您还可以让JVM在崩溃时将其内存转储到文件中,然后使用visualvm分析该文件。你应该看到有一件事是抓住你所有的记忆。我怀疑它是所有的URL。

答案 5 :(得分:1)

这里已经有几位专家,所以,我会对问题进行简要介绍:

  1. 不恰当地使用String Builder:
  2. StringBuilder builder = new StringBuilder();

    try {
        inputStream = new FileReader(f);
        int c;
        char d;
    
        while ((c = inputStream.read()) != -1) {
            d = (char)c;
            builder.append(d);
        }
    }
    

    当您一次处理少量数据时,Java很漂亮,请记住垃圾收集器。

    相反,我建议您一次读取文件(文本文件)1行,处理行,继续前进,永远不要创建StringBuilder的巨大内存球,只是为了得到一个字符串,

    你的文本文件的大概是1 GB大小,你已经完成了交配。

    1. 在阅读文件时添加实际流程(如第1项所述)

    2. 你不需要再次关闭InputStream,finally块中的代码就足够了。

    3. 问候

答案 6 :(得分:0)

如果链表占用你的内存,那么之后分配内存的每个命令都可能因OOM错误而失败。所以这看起来像你的问题。

答案 7 :(得分:0)

您正在将文件读入内存。至少有一个文件太大而无法放入默认的JVM堆中。您可以允许它在-Xmx1g之后的命令行中使用java之类的arg来使用更多内存。

顺便说一下,一次读取一个字符的文件效率非常低!

答案 8 :(得分:0)

而不是尝试拆分字符串(基本上根据拆分创建一个子字符串数组) - 因此每次使用slpit时使用的内存超过两倍,你应该尝试基于正则表达式匹配开头和结束模式,逐个提取单个子字符串,然后从中提取URL。

另外,如果您的文件很大,我建议您甚至不要将所有内容一次加载到内存中...将其内容流式传输到缓冲区(可管理的大小)并使用基于模式的搜索(并且在逐步浏览文件内容时继续删除/添加更多缓冲区。)

实现会稍微减慢程序速度,但会使用相当少的内存。

答案 9 :(得分:0)

您的代码中的一个主要问题是您将整个文件读入字符串构建器,然后将其转换为字符串,然后将其拆分为更小的部分。因此,如果文件大小很大,您将遇到麻烦。正如其他人所建议的那样,逐行处理文件,因为这样可以节省大量内存。

此外,您应该在处理每个文件后检查列表的大小。如果大小非常大,您可能希望使用不同的方法或通过-Xmx选项增加进程的内存。