Java内存使用/线程池性能问题

时间:2011-07-26 15:15:52

标签: java performance memory visualvm jvisualvm

这些事情显然需要仔细检查和提供代码,以便彻底分析并提供好的建议。然而,这并不总是可行的,我希望根据我在下面提供的信息为我提供好的提示。

我有一个服务器应用程序,它使用侦听器线程来侦听传入的数据。传入的数据被解释为特定于应用程序的消息,然后这些消息就会引发事件。

到目前为止,我并没有真正控制事情的进展。

因为这是遗留应用程序,所以这些事件以前由同一个侦听器线程(主要是单线程应用程序)处理。事件被发送到黑盒子,然后输出应该写入磁盘的结果。

为了提高吞吐量,我想使用线程池来处理事件。我们的想法是,每次创建事件时,侦听器线程都可以生成新任务,并且线程将负责黑盒调用。最后,我有一个后台线程执行写入磁盘。

只需使用以前的设置和后台编写器,一切正常,吞吐量比以前增加了1.6倍。

当我添加线程池时,性能会下降。一开始,一切似乎都运行顺利,但过了一段时间,一切都很慢,最后我得到OutOfMemoryExceptions。奇怪的是,当我每次将一个任务添加到池中时打印活动线程的数量(以及有关排队多少任务的信息等等),看起来好像线程池没有问题跟上producer(监听器线程)。

使用top -H检查CPU使用情况,它在开始时非常均匀分布,但最后工作线程几乎没有活动,只有侦听器线程处于活动状态。然而它似乎没有提交更多的任务......

有人可以假设出现这些症状的原因吗?您是否认为遗留代码(我无法控制)中的某些内容更可能在添加多个线程时变坏?内存不足的问题应该是因为某个队列的某个地方变得太大了,但由于线程池几乎从不包含排队的任务,所以不可能这样。

欢迎任何想法。特别是关于如何更有效地诊断这种情况的想法。我怎样才能更好地了解我的线程在做什么等等。

感谢。

4 个答案:

答案 0 :(得分:5)

减速然后内存不足意味着内存泄漏。

因此,我首先使用一些Java内存分析器工具来确定是否存在泄漏以及泄漏的内容。有时候你很幸运,泄露的物体是众所周知的,很明显谁会挂在他们不应该做的事情上。

答案 1 :(得分:4)

感谢您的回答。我读了Java VisualVM并将其用作工具。结果和结论详述如下。希望这些图片能够运作足够长的时间。

我首先运行程序并创建了一些堆转储,以为我可以分析转储并查看占用所有内存的内容。这可能是有效的,除了转储文件如此之大,我的工作站在尝试访问它时的用途有限。在等待两个小时进行一次操作后,我意识到我无法做到这一点。

所以我的下一个选择是愚蠢的,没想过的。我可以减少发送到应用程序的消息数量,并且应该仍然存在增加内存使用量的趋势。此外,转储文件将更小,更快分析。

事实证明,当以较慢的速率发送消息时,不会发生内存不足的问题!内存使用情况的图表如下所示。

slow send http://img197.imageshack.us/img197/1628/slowsend.png

峰值是累积内存分配的结果,随后的低谷是在垃圾收集器运行之后。虽然内存使用量肯定是相当惊人的,并且可能存在问题,但是没有观察到内存泄漏的长期趋势。

我开始逐步增加每秒发送的消息的速率,以查看应用程序到达墙的位置。下面的图像显示了与前一个非常不同的场景...

fast send http://img200.imageshack.us/img200/151/fastsend.png

因为当发送的消息速率增加时会发生这种情况,我的猜测是我释放侦听器线程导致它能够非常快速地接受大量消息,这会导致越来越多的分配。垃圾收集器没有运行,内存使用率也不见了。

对于这个问题当然还有更多,但鉴于我今天发现的东西,我对从这里去哪里有了相当清楚的认识。当然,欢迎任何其他建议/意见。

这个问题应该重新归类为处理内存使用而不是线程池......线程池根本不是问题。

答案 2 :(得分:2)

我同意@djna。 java并发包的线程池工作原理。如果它不需要它,它不会创建线程。您可以看到线程数量符合预期。这意味着遗留代码中的某些东西可能还没有为多线程做好准备。例如,某些代码片段未同步。因此,某些元素不会从集合中删除。或者一些其他元素存储在集合中。因此,内存使用量正在增长。

顺便说一下,我不明白应用程序的哪个部分现在使用了线程池。您是否有一个处理事件的线程,现在您有多个线程执行此操作?你有可能改变了线程间的通信机制吗?添加了队列?这可能是你调查的另一个方向。

祝你好运!

答案 3 :(得分:1)

正如djna所提到的,它可能是某种类型的内存泄漏。我的猜测是你在某个地方保留了对请求的引用:

  • 在排队请求的调度程序线程中
  • 在处理请求的线程中
  • 在处理请求的黑匣子中
  • 在写入磁盘的写入程序线程中。

由于你说在将线程池添加到混合之前一切正常,我的猜测是池中的线程在某处保持对请求的引用。我的想法是,如果没有线程池,你就不会重复使用线程,所以信息就会消失。

根据djna的建议,您可以使用Java内存分析器来帮助确定数据堆叠的位置。