使用多个线程使垃圾收集器使用100%的CPU时间

时间:2014-04-02 09:37:53

标签: java multithreading garbage-collection

我逐行阅读了1,3GB的文本文件。我提取并格式化内容以满足我的需要,并将其再次保存到新的文本文件中。

最初我只使用了主线程。但是提取和格式化需要大量的CPU时间,我想通过多线程来加速它。

但那是我的个人资料显示的内容: enter image description here

当我开始使用多个线程时,垃圾收集器时间上升到100%。因此会抛出java.lang.OutOfMemoryError: GC overhead limit exceeded个错误。

我有一个处理单行的函数,我在newFixedThreadPool内执行它。如果我将一个或四个线程分配给池,则无关紧要。

使用不同的分析器,我无法找出导致问题的代码。当我只使用主线程时,我不明白为什么我的GC为0,0%。

有没有人在不看代码的情况下有想法?


更新:我试图抽象一些代码:

A.java

ExecutorService executor = Executors.newFixedThreadPool(4);

while((line = reader.readLine()) != null) {
    Runnable processLine = new Runnable() {
        private String line;

        private Runnable init(String line) {
            this.line = line;
            return this;
        }

        @Override
        public void run() {
            processLine(line); // @B.java
        }
    }.init(line);

    executor.execute(processLine);
}

B.java

public int processLine(String line) {
    String[][] outputLines = new String[x][y];
    String field;

    for(... x ...) {
        for(... y ...) {
            field = extractField(line); // @C.java
            ...
            outputLines[x][y] = formatField(field); // @C.java
        }
    }

    write(outputLines); // write the generated lines to BufferedWriter(s)
}

C.java

public String extractField(String line) {
    if(filetype.equals("csv") {
        String[] splitLine = line.split(";");

        return splitLine[position];
    }
    ...
}

public String formatField(String field) {
    if(trim == true) {
        field = field.trim();
    }
    ...
}

1 个答案:

答案 0 :(得分:2)

我希望您的应用程序使用接近所有可用堆空间。这将导致越来越多的JVM时间用于运行垃圾收集器......徒劳地尝试回收空间。这正是GC开销限制设计要处理的情况。

简而言之,要么存储泄漏,要么应用程序需要更多内存。


  

我有一个处理单行的函数,我在newFixedThreadPool中执行它。如果我将一个或四个线程分配给池,则无关紧要。

强烈暗示(对我而言)导致问题的不是线程,而是实现多线程的方式。


<强>更新

我认为你问题的根本原因是:

    ExecutorService executor = Executors.newFixedThreadPool(4);

请注意,javadoc表示该方法使用无界工作队列创建执行程序。

假设您的读者线程(执行A代码的线程)可以比执行处理的线程更快地读取和排队行,可以处理,格式化和输出它们。

在这种情况下,会发生的情况是执行程序的工作队列将变得更长,更长并且......最终它将变得如此之长以至于堆接近完整的可到达对象。这将导致GC花费很长时间跟踪队列,你会得到一个OOME。

如果这种情况在实践中诞生,那么您的应用程序就会出现内存泄漏的不良方面......即使您认为它实际上并不是泄漏。

解决方案很简单,使用有界工作队列创建执行程序。您需要通过直接实例化ThreadPoolExecutor并提供适当配置的工作队列对象来实现此目的。