堆大小问题 - 使用Java进行内存管理

4
我在我的应用程序中有以下代码,它会执行两件事情:
解析包含'n'个数据的文件。
对于文件中的每个数据,都会进行两个Web服务调用。
 public static List<String> parseFile(String fileName) {
   List<String> idList = new ArrayList<String>();
   try {
     BufferedReader cfgFile = new BufferedReader(new FileReader(new File(fileName)));
     String line = null;
     cfgFile.readLine();
     while ((line = cfgFile.readLine()) != null) {
       if (!line.trim().equals("")) {
         String [] fields = line.split("\\|"); 
         idList.add(fields[0]);
       } 
     } 
     cfgFile.close();
   } catch (IOException e) {
     System.out.println(e+" Unexpected File IO Error.");
   }
 return idList;
}

当我尝试解析拥有一百万行记录的文件时,Java进程在处理一定量的数据后就会失败。我遇到了java.lang.OutOfMemoryError: Java heap space错误。我可以部分地理解由于提供了大量数据而导致Java进程停止运行。请建议我如何处理这么多的数据。
编辑:这段代码new BufferedReader(new FileReader(new File(fileName)));是否会解析整个文件并受到文件大小的影响?
3个回答

3
您的问题在于您正在累积列表中的所有数据。最好的方法是以流式方式处理。这意味着不要在列表上累积所有id,而是在每一行上调用您的Web服务或累积较小的缓冲区,然后再进行调用。
打开文件并创建BufferedReader对内存消耗没有影响,因为文件的字节将逐行(或多或少)读取。问题出现在代码的这个点:idList.add(fields[0]);,当您始终将所有文件数据累积到其中时,列表的大小将变得与文件一样大。
您的代码应该像这样做:
 while ((line = cfgFile.readLine()) != null) {
   if (!line.trim().equals("")) {
     String [] fields = line.split("\\|"); 
     callToRemoteWebService(fields[0]);
   } 
 } 

2
使用-Xms和-Xmx选项增加Java堆内存大小。如果没有显式设置,JVM会将堆大小设置为符合人体工程学的默认值,这在您的情况下是不够的。阅读此论文以了解有关调整JVM中的内存的更多信息:http://www.oracle.com/technetwork/java/javase/tech/memorymanagement-whitepaper-1-150020.pdf
编辑:以生产者-消费者方式进行并行处理的另一种方法。总体思路是创建一个生产者线程来读取文件并排队处理任务,n个消费者线程来消费它们。一个非常普遍的想法(仅供说明目的)如下:
// blocking queue holding the tasks to be executed
final SynchronousQueue<Callable<String[]> queue = // ...

// reads the file and submit tasks for processing
final Runnable producer = new Runnable() {
  public void run() {
     BufferedReader in = null;
     try {
         in = new BufferedReader(new FileReader(new File(fileName)));
         String line = null;
         while ((line = file.readLine()) != null) {
             if (!line.trim().equals("")) {
                 String[] fields = line.split("\\|"); 
                 // this will block if there are not available consumer threads to process it...
                 queue.put(new Callable<Void>() {
                     public Void call() {
                         process(fields);
                     }
                  });
              } 
          }
     } catch (InterruptedException e) {
         Thread.currentThread().interrupt());
     } finally {
         // close the buffered reader here...
     }
  }
}

// Consumes the tasks submitted from the producer. Consumers can be pooled
// for parallel processing.
final Runnable consumer = new Runnable() {
  public void run() {
    try {
        while (true) {
            // this method blocks if there are no items left for processing in the queue...
            Callable<Void> task = queue.take();
            taks.call();
        }
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
  }
}

当然,你需要编写管理消费者和生产者线程生命周期的代码。正确的方法是使用 Executor 实现。


谢谢分享你的想法。但是我对我的代码不满意。我能否从代码层面处理内存管理问题?另外,我想告诉你,我有权限更改生产环境中设置的堆内存大小。 - Arun
你的代码确实需要重构以改善内存利用率。这个想法是,不要维护所有项目的列表(因此会使用更多的内存),而是考虑使用执行器框架异步处理每个项目。我会更新我的答案,告诉你如何做到这一点。 - Lefteris Laskaridis
有没有一种方法可以找到堆大小... 我想在更改代码后监视堆大小。 - Arun
是的,确定堆运行时大小有两种方法。第一种是使用分析器(例如 http://profiler.netbeans.org/)对应用程序进行分析,第二种是检查包括 -XX:+PrintGCDetails jvm 选项产生的垃圾回收输出。此选项会在每次主要和次要收集之前输出堆大小。通过检查输出,您可以看到应用程序使用了多少堆大小以及这是如何分配在各个代之间的。 - Lefteris Laskaridis
我想在Unix系统中检查堆大小。 是否有命令可以使用Unix命令来检查大小?请建议...... - Arun
1
我所知道的另一种方法,如果您不想使用GC输出或分析器,就是从shell使用jmap命令。为了做到这一点,您首先必须通过发出jps命令(请参阅http://docs.oracle.com/javase/6/docs/technotes/tools/share/jps.html)找出您的Java应用程序的进程ID。该命令将列出当前在系统中运行的所有JVM进程ID。一旦您找到应用程序的pid,然后运行jmap -heap pid,其中pid是您的应用程序的进程ID。(请参阅http://docs.oracle.com/javase/6/docs/technotes/tools/share/jmap.html) - Lefteris Laskaridis

1

当你想要处理大数据时,有两种选择:

  1. 使用足够大的堆来容纳所有数据。这种方法在一段时间内可以“工作”,但如果你的数据大小是无限的,它最终会失败。
  2. 逐步处理数据。只在任何时候将部分数据(有界大小)保留在内存中。这是理想的解决方案,因为它可以扩展到任何数量的数据。

在我看来,第一个选项不是解决方案,因为我无法增加磁盘大小。目前我正在执行提到的第二个选项。谢谢回复。:) - Arun
@Arun - 是的,我试图表明选项1并不是真正的解决方案。话虽如此,我不确定磁盘大小与任何事情有关...? - jtahlborn
抱歉,我指的是我的生产环境中Java堆的大小,但我没有权限访问。 - Arun

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接