【问题标题】:Java threads slow down towards the end of processingJava 线程在处理结束时变慢
【发布时间】:2013-04-10 11:33:02
【问题描述】:

我有一个 Java 程序,它接收一个包含文本文件列表的文本文件,并分别处理每一行。为了加快处理速度,我使用带有 24 个线程的 FixedThreadPool 的 ExecutorService 线程。该机器有 24 个内核和 48GB 内存。

我正在处理的文本文件有 250 万行。我发现对于前 230 万行左右的内容,在 CPU 利用率很高的情况下运行得非常好。但是,超过某个点(大约 2.3 行),性能下降,只使用一个 CPU,我的程序几乎停止运行。

我调查了许多原因,确保我的所有文件句柄都已关闭,并增加了提供给 JVM 的内存量。然而,无论我改变什么,性能总是会在最后下降。我什至尝试过包含较少行的文本文件,但在处理文件结束时性能再次下降。

除了标准的 Java 并发库之外,代码还利用 Lucene 库进行文本处理和分析。

当我不线程化这段代码时,性能是恒定的,并且不会在最后退化。我知道这是在黑暗中拍摄,很难描述发生了什么,但我想我想看看是否有人对最终导致性能退化的原因有任何想法。

编辑

在收到 cmets 后,我粘贴了堆栈跟踪 here。如您所见,似乎没有任何线程正在阻塞。此外,在进行分析时,当事情变慢时,GC 并没有达到 100%。事实上,大部分时间 CPU 和 GC 利用率都为 0%,CPU 偶尔会出现峰值以处理一些文件,然后再次停止。

执行线程的代码

 BufferedReader read = new BufferedReader(new FileReader(inputFile));
 ExecutorService executor = Executors.newFixedThreadPool(NTHREADS);
 String line;
 while ((line = read.readLine()) != null) { //index each line
     Runnable worker = new CharikarHashThreader(line, bits, minTokens);
     executor.execute(worker);
 }
 read.close();

【问题讨论】:

  • 您是否在分析器中检查过泄漏等情况?你确定没有什么东西可以让你的线程死锁吗?你能给我们看一些代码吗?
  • 所以还有 200k 行在排队,它变慢了吗?您是否看过内存以确保它不在 GC 地狱中。您是一次排队排队还是排队?
  • 您绝对应该使用大小有限的BlockingQueue,而不是一次将所有行都排队。
  • 正如@Gray 所说,当 JVM 在一个 CPU 的 100% 处停止时,GC 似乎是一个合理的猜测......缺少分析,您是否尝试过关闭 Lucene 以查看减速是否与你的那部分代码?您是否确保您没有在某种集合中使用非恒定时间的 put/remove/get 操作堆积操作? (例如,搜索 LinkedList 的特定元素,弹出 ArrayList 的随机元素,...)
  • 我也尝试过一百万行,但在某些时候它也会变慢。基于所有的 cmets,这绝对是一个 GC 问题。我将研究这里提出的一些建议,并在更多实验后报告。谢谢!

标签: java multithreading lucene java.util.concurrent


【解决方案1】:

这听起来很像垃圾收集/内存问题。

当垃圾收集运行时,它会暂停所有线程,以便 GC 线程可以进行“这是可收集的垃圾”分析,而不会对其进行任何更改。当 GC 运行时,您会看到正好 1 个线程处于 100%,而其他线程将停留在 0%。

我会考虑添加一些 Runtime.freeMemory() 调用(或使用分析器)来查看在 GC 期间是否发生“停止”。

我还尝试仅在文件的前 10k 行上运行您的程序,看看是否可行。

我还想看看你的程序在应该使用 StringBuilders 时是否构建了太多的中间字符串。

在我看来,您需要分析您的内存使用情况。

【讨论】:

  • 有一个线程处于 100%,而另一个线程被卡住正是正在发生的事情。我将研究其中的一些建议并进行报告。谢谢!
  • 我不确定这是否是 GC 问题。我正在使用 VisualVM 进行分析,当网格停止时,CPU 使用率和 GC 使用率都为 0% 或接近它。但是,我所有的线程现在都处于“等待”状态,所以这似乎与线程有关,而不是内存。
  • 这也与内存使用量的大幅增加相对应,直到事情陷入停顿。
  • 另外,不确定是否相关,但 java.util.LinkedList$Node 目前正在使用大部分内存
  • 听起来像使用 ArrayList 而不是 LinkedList 可以节省与在 LinkedList 中存储非常大的列表相关的内存开销
【解决方案2】:

我最初认为这也是 GC 问题,但我不太确定是否提供以下信息。

我什至尝试过包含更少行的文本文件,但在处理文件结束时性能再次下降。

我的猜测是线程没有退出,但不知何故被卡住了。我建议进行线程转储(*nix 下的kill -QUIT pid 或使用jstack)并查看线程在哪里。这将帮助您确定它们是否被卡在某个地方。

我怀疑您的程序开始时运行了 24 个线程,但随着时间的推移,您会失去一个,然后又失去一个。虽然最后似乎性能急剧下降,但我想知道程序是否从一开始就变得越来越慢。

  • 注意没有正确连接或 IO 超时的套接字。
  • 可能是某种阻塞线程的锁争用?
  • 也许 Lucene 正在做的事情要么导致争用,要么阻塞你的线程。正如@GPI 所提到的,我会尝试注释掉 Lucene 调用,看看问题是否消失。同样,堆栈跟踪也会向您显示这一点。

确定线程阻塞的位置后,您需要解决锁定问题、为网络调用添加超时或以其他方式解决问题。

希望这会有所帮助。

【讨论】:

  • 感谢格雷的回复。我已经编辑了我的原始帖子,其中包含指向堆栈跟踪的链接。如您所见,似乎没有任何线程正在阻塞。大多数时候 GC 利用率也为 0%。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-01-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-29
  • 1970-01-01
相关资源
最近更新 更多