2012-04-15 58 views
0

我有以下JAVA类从包含许多制表符分隔字符串行的文件读取。一个例子是线类似如下:JAVA处理文件java.lang.OutOfMemoryError:GC开销限制超出错误

GO:0085044  GO:0085044  GO:0085044 

该代码读取每一行,并使用分裂功能把三个子串到一个数组,然后它把它们放入一个两级散列。

public class LCAReader { 
    public static void main(String[] args) { 
     Map<String, Map<String, String>> termPairLCA = new HashMap<String, Map<String, String>>(); 
     File ifile = new File("LCA1.txt"); 
     try { 
      BufferedReader reader = new BufferedReader(new FileReader(ifile)); 
      String line = null; 
      while((line=reader.readLine()) != null) { 
       String[] arr = line.split("\t"); 
       if(termPairLCA.containsKey(arr[0])) { 
        if(termPairLCA.get(arr[0]).containsKey(arr[1])) { 
         System.out.println("Error: Duplicate term in LCACache"); 
        } else { 
         termPairLCA.get(arr[0]).put(new String(arr[1]), new String(arr[2])); 
        } 
       } else { 
        Map<String, String> tempMap = new HashMap<String, String>(); 
        tempMap.put(new String(arr[1]), new String(arr[2])); 
        termPairLCA.put(new String(arr[0]), tempMap); 
       } 
      } 
      reader.close(); 
     } catch (IOException e) { 
      System.out.println(e.getMessage()); 
     } 
    } 
} 

当我运行该程序时,运行一段时间后出现以下运行时错误。我注意到内存使用量不断增加。

在线程异常 “主要” java.lang.OutOfMemoryError:GC开销限制在java.util.regex.Pattern.compile(Pattern.java:1469) 在java.util.regex.Pattern中超过 (模式。 。java.lang.String.split(String.java:2304) at java.lang.String.split(String) (LCAReader.java:17)

输入文件几乎是2G,我运行程序的机器有8G内存。我也尝试了-Xmx4096m参数来运行该程序,但这并没有帮助。所以我想我的代码中有一些内存泄漏,但我找不到它们。

任何人都可以帮助我吗?提前致谢!

回答

3

没有内存泄漏;你只是想存储太多的数据。 2GB的文本将需要4GB的RAM作为Java字符;加上每个字符串对象开销大约有48个字节。假设文本是100个字符行,大约有另一个GB,总共5GB - 我们甚至还没有计算出Map.Entry对象!你需要一个至少保守的Java堆,6GB来运行你的数据上的这个程序,或许更多。

您可以通过几件简单的事情来改善这一点。首先,失去构造函数new String() - 它们没用,只是让垃圾收集器更加努力。字符串是不可变的,所以你永远不需要复制它们。其次,您可以使用intern池共享重复的字符串 - 这可能会或可能不会有所帮助,具体取决于数据的实际外观。但是,您可以尝试,例如,

tempMap.put(arr[1].intern(), arr[2].intern()); 

这些简单的步骤可能会有所帮助。

+2

使用'String.split()'时,谨慎使用'new String()'的一个词。如果你只需要'String.split()'的结果中的几个标记,那么使用'new String()'通常是一个好主意,因为'String.split()'返回的'Strings'只是包装器指向整个拆分字符串。所以你最终会把你的整个字符串放在堆上,当你想要的只是它的一小部分时(这里没有什么大不了的,因为他使用'String'中的所有东西,而不是'\ t'分隔符)。 – ulmangt 2012-04-15 03:41:39

+0

我尝试了intern()方法并将堆大小增加到6G,并没有显示异常。从内存使用情况来看,我可以看到它仍然使用大量的内存。所以增加堆大小帮助了很多,而intern()方法没有什么帮助。谢谢! – Wei 2012-04-16 02:42:56

0

我没有看到任何泄漏,您只需要非常大量的内存来存储您的地图。 有一个很好的验证工具:使用选项 - XX:+ HeapDumpOnOutOfMemoryError制作堆转储,并将其导入到独立版本的Eclipse Memory Analyzer中。它可以显示最大的保留对象和可能阻止垃圾回收器完成其工作的引用树。 此外,探查器如Netbeans Profiler可以给你很多有趣的实时信息(例如检查字符串和字符实例的数量)。

此外,将代码拆分为不同的类(每个类具有不同的可响应性)是一种很好的做法:一边是“两键映射”类(TreeMap),另一边是“解析器”类,它应该使调试更容易...

这绝对不是一个好主意,这个巨大的地图内存储...或者你需要用一些较小的文件制作一个基准,然后外推得到估计的RAM,你需要在系统上有适合你的情况......并将Xmx设置为合适的值。 为什么不使用Key Value store如Berckley DB:比关系数据库更简单,并且应该完全适合您需要的两级索引。 检查这个职位商店的选择:key-value store suggestion

好运

0

你可能不应该使用String.split和存储信息作为纯String,因为这会产生大量的飞行String对象。

尝试使用基于char的方法,因为您的格式似乎相当固定,因此您可以知道一行上不同数据点的确切指标。

如果您在试验中多做一点尝试,可以尝试使用NIO支持的方法,使用内存映射DirectByteBufferCharBuffer来遍历文件。在那里,您可以将不同数据点的标记标记为标记对象,并且只在需要时才在过程中加载实际的String数据。

相关问题