我正在努力弄清楚造成这种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有关呢?
答案 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)
因为这是
我建议你给你的JVM一个适合你RAM的最大堆大小限制。
要使用更少的内存,我会使用缓冲的阅读器来拉入整行,并节省临时对象的创建。
答案 3 :(得分:1)
简单的答案是:您不应该将文本文件中的所有URL加载到内存中。你肯定是这样做的,因为你想在下一步中处理它们。因此,不要将它们添加到内存中的List,而是执行下一步(可能存储在数据库中或检查它是否可访问)并忘记该URL。
答案 4 :(得分:1)
你有多少个URL?看起来你只是储存了比你能处理的更多的东西。
据我所知,链表是唯一没有限定在循环内的对象,因此无法收集。
对于OOM错误,抛出它的位置并不重要。
要正确检查,请使用分析器(查看JVisualVM以获取免费分析器,您可能已经拥有它)。您将看到堆中有哪些对象。您还可以让JVM在崩溃时将其内存转储到文件中,然后使用visualvm分析该文件。你应该看到有一件事是抓住你所有的记忆。我怀疑它是所有的URL。
答案 5 :(得分:1)
这里已经有几位专家,所以,我会对问题进行简要介绍:
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项所述)
你不需要再次关闭InputStream,finally块中的代码就足够了。
问候
答案 6 :(得分:0)
如果链表占用你的内存,那么之后分配内存的每个命令都可能因OOM错误而失败。所以这看起来像你的问题。
答案 7 :(得分:0)
您正在将文件读入内存。至少有一个文件太大而无法放入默认的JVM堆中。您可以允许它在-Xmx1g
之后的命令行中使用java
之类的arg来使用更多内存。
顺便说一下,一次读取一个字符的文件效率非常低!
答案 8 :(得分:0)
而不是尝试拆分字符串(基本上根据拆分创建一个子字符串数组) - 因此每次使用slpit时使用的内存超过两倍,你应该尝试基于正则表达式匹配开头和结束模式,逐个提取单个子字符串,然后从中提取URL。
另外,如果您的文件很大,我建议您甚至不要将所有内容一次加载到内存中...将其内容流式传输到缓冲区(可管理的大小)并使用基于模式的搜索(并且在逐步浏览文件内容时继续删除/添加更多缓冲区。)
实现会稍微减慢程序速度,但会使用相当少的内存。
答案 9 :(得分:0)
您的代码中的一个主要问题是您将整个文件读入字符串构建器,然后将其转换为字符串,然后将其拆分为更小的部分。因此,如果文件大小很大,您将遇到麻烦。正如其他人所建议的那样,逐行处理文件,因为这样可以节省大量内存。
此外,您应该在处理每个文件后检查列表的大小。如果大小非常大,您可能希望使用不同的方法或通过-Xmx选项增加进程的内存。