2011-11-07 71 views
3

我发现我的程序正在增加的内存是因为下面的代码,目前我正在阅读一个大约7GB的文件,而且我相信存储在哈希集中的文件是10M的课程,但内存我的程序不断增加到300MB,然后由于OutofMemoryError而崩溃。如果是Hashset问题,我应该选择哪种数据结构?为什么我的hashset如此消耗内存?

if(tagsStr!=null) { 
     if(tagsStr.contains("a")||tagsStr.contains("b")||tagsStr.contains("c")) { 
      maTable.add(postId); 
     } 
    } else { 
     if(maTable.contains(parentId)) { 
      //do sth else, no memories added here 
     } 
    } 
+1

我认为这不太可能是一个HashSet的问题,除非你把很多的数据。你正在存储的字符串的大小是多少?你是否将整个文件一次读入内存或一行?您在这里提供的数据并不能提供足够的信息来帮助您。 –

+0

表格在撞击之前包含多少物品? –

+1

元素的平均长度/大小是多少? –

回答

2

您已经有内存泄漏,或者您对正在存储的字符串数据量的理解不正确。如果没有看到更多的代码,我们无法分辨。

科学解决方案是使用内存分析器运行应用程序,并分析输出以查看哪些数据结构正在使用意外大量的内存。


如果我是猜测,这将是您的应用程序(在一定程度上)正在做这样的事情:

String line; 
while ((line = br.readLine()) != null) { 
    // search for tag in line 
    String tagStr = line.substring(pos1, pos2); 
    // code as per your example 
} 

这将使用比你期望很多更多的内存。调用substring(...)将创建一个tagStr对象,该对象引用原始line字符串的后备数组。您希望短的标记字符串实际上指的是保存原始行中所有字符的char[]对象。

修复方法是做到这一点:

String tagStr = new String(line.substring(pos1, pos2)); 

此创建不共享参数字符串的背衬阵列字符串对象。

UPDATE - 这个或类似的东西越来越可能的解释......给你最新的数据。


为了扩大Jon Skeet的观点,一个小字符串的开销是惊人的高。例如,在一个典型的32位的JVM中,一个字符的字符串的内存使用情况是:对字符串对象

  • 字符串对象标头:2个词语
  • 字符串对象字段:3个词语
  • 填充:1字(我认为)
  • 支持数组对象头:3个词语
  • 支持数组数据:1个字

总计:10个字 - 40个字节 - 持有一个char数据...或者一个byte数据如果您的输入是8位字符集。

(这是不足以解释你的问题,但你应该知道的也无妨。)

+0

我想补充一点,通常也可以共享Strings的后备阵列,从而减少内存消耗。这取决于多少个字符串共享一个支持数组,以及支持数组的哪一部分未被任何字符串使用。 – jmg

+0

这在理论上是可行的,但在OP的情况下似乎不太可能。 –

6

你还没有真正告诉我们,你在做什么,但:

  • 如果你的文件正在像ASCII,每次读取的字符将文件中的一个字节或内存中有两个字节。
  • 每个字符串都会有一个对象的开销 - 如果你存储大量的小弦的这可能是显著
  • 如果您正在阅读与BufferedReader(或大串子取)线,每一个可能有一个大后备缓冲区 - 您可能想要使用maTable.add(new String(postId))来避免这种情况
  • 哈希集中的每个条目都需要一个单独的对象来保留键/哈希码/值/下一条目值。再次,有很多条目,这可以加起来

总之,这是很有可能的,你没有做错任何事情,但增加内存因素的组合对你不利。这些大部分都是不可避免的,但第三个可能是相关的。

+0

你确定你的第三点 - “用BufferedReader读线”?我认为BufferedReader注意自己使用新的String(...)。我赞同关于子串的一点。 –

+0

@PaulCager:这当然不是我最后一次看。最后我检查了一下,它读入了一个缓冲区字符数组(默认为80字节IIRC),然后创建了一个新的String,它是该字符数组的视图。如果数组比“可用字符串”大得多,那么您可能会浪费大量内存。这是前一阵子,所以它可能已经改变了。 –

+0

它看起来已经改变 - 它现在要么返回通过StringBuffer构建的String(如果溢出cb),要么执行“str = new String(cb,startChar,i - startChar);”其中cb是缓冲区。 –

0

无法被有可能,读入内存(从7G文件)中的数据由于某种原因没有被释放? ike Jon放的东西...即。由于字符串是不可变的,所以每读取一个字符串都需要一个新的String对象创建,如果GC不够快,可能会导致内存不足...如果上述情况比您可能会在代码中插入一些“断点” /迭代,即。在某些特定的点上,发出gc并等到它终止。

+0

如果GC不够快,您将无法获得OOM。如果有必要,GC会暂停整个虚拟机并在投掷OOM之前先停止收集世界。 –

+0

感谢您的回复。实际上,我并不知道如果需要的话,GC会在阻塞模式下自动触发:)但是,即使使用这种方法,OOM仍然可能会出现。看到相关的问题:http://stackoverflow.com/questions/1393486/what-does-the-error-message-java-lang-outofmemoryerror-gc-overhead-limit-excee,特别是它引用的官方文章:http: //www.oracle.com/technetwork/java/javase/gc-tuning-6-140523.html#par_gc.oom – Gyula

+0

“如果在垃圾收集中花费了太多时间,并行收集器将抛出OutOfMemoryError:如果多于总时间的98%用于垃圾回收,并且只有不到2%的堆被回收,则会抛出OutOfMemoryError。“这就是说,如果GC有太多工作要做(太多的对象太自由),可能会抛出一个OOM。因此在这种情况下,应用驱动的GC可能会有所帮助,可能不会? – Gyula

0

运行程序-XX:+ HeapDumpOnOutOfMemoryError。然后你就可以使用内存分析器如MAT来查看所有内存的使用情况 - 这可能是完全意想不到的。

+0

谢谢,我尝试使用MAT,但它保持这样失败:倾倒堆java_pid4080.hprof ... 转储文件不完整:没有足够的空间有没有什么办法来解决这个问题? – faz

+1

听起来你已经用完了磁盘空间。 –