使用Executors.newFixedThreadPool()的内存泄漏

时间:2012-12-17 08:51:07

标签: java multithreading performance java-ee jms

我在一个独立的环境中使用Spring3.1。

(这个问题不一定与Spring有关。它在独立的环境中也表现相同。)

我实现了一个从Topic接收消息的监听器。消息率非常高(约20/30 m / s)。

某些消息可能比其他消息花费更多的处理时间。

侦听器使用相同的实例,这意味着如果一条消息被处理的时间太长,它会对我们的表演产生很大影响。

我们考虑过使用自己的对象池而不是使用相同的侦听器实例,但后来我找到了 Executors (java.util.concurrent.Executors)。

因此,对于收到的每条消息,将分配一个不同的线程。这将确保我们的侦听器实例可以自由地并行处理消息。

private ExecutorService  threadPool = Executors.newFixedThreadPool(100);
    @Override
    public void onMessage(final Message msg)
    {
        Runnable t = new Runnable()
        {
            public void run()
            {
                onSessionMessage(msg);
                log.trace("AbstractSessionBean, received messge");
            }
        };
        threadPool.execute(t);
    }

这似乎解决了我们的性能问题。但在使用jconsole监控应用程序后,我们现在面临巨大的内存泄漏。

堆内存使用量在时间上显着增加。

所以我尝试使用FixedThreadPool大小编号“玩”一下。仍有大量内存使用:

enter image description here

我知道如何解决这个问题?还有其他想法来解决我的关键问题吗?

jconsole after performing GB

jconsole overall view

运行堆转储后,我遇到了两个问题:

Head Dump

感谢, 射线。

4 个答案:

答案 0 :(得分:2)

我认为你对内存泄漏没有任何问题,我至少无法从你的jconsole图表中看到它。没有主要的GC收集。因此,似乎只有越来越多的对象被分配到终身(旧)代。为了确保内存泄漏,您应该执行GC,然后比较分配的内存。如果发现泄漏,您可以使用jmap或可视化工具(标准JDK工具)进行堆转储。可以使用MAT分析此堆转储。在进行堆转储之前,最好执行GC以减少堆转储文件大小。

一些注意事项:

  • 线程计数不应显式影响堆内存。您可以查看下一个java memory structure。所以线程需要堆栈内存而不是堆。
  • 一般来说,由于GC工作算法,不为重物提供缓存不是一个好主意。
  • 另外我认为您应该考虑cached thread pool或根据服务器硬件校准ThreadPoolSize。

答案 1 :(得分:1)

我相信你遇到的问题是threadPool没有释放它的资源。完成提交或执行后,需要调用threadPool.shutdown()。这将等到任务完成后再终止线程,然后可以进行垃圾回收。

来自官方Java api网站:

"应关闭未使用的ExecutorService以允许回收其资源。" https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ExecutorService.html#shutdown()

或者你可以使用newCachedThreadPool()"创建一个根据需要创建新线程的线程池,但是当它们可用时将重用先前构造的线程"见https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Executors.html

当我遇到这个问题时,我选择了newFixedThreadPool()和shutdown选项。

答案 2 :(得分:0)

在使用ExecutorService之后,您需要停止线程池,因为它是相同的资源,如文件或数据库或需要显式发布的任何其他内容。 ExecutorService有方法shutdown()和shutdownNow(),可以在finally块中使用gargabe collect。

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

class SimpExec {
  public static void main(String args[]) {
    CountDownLatch countDownLatch = new CountDownLatch(5);
    CountDownLatch CountDownLatch2 = new CountDownLatch(5);

    ExecutorService eService = Executors.newFixedThreadPool(2);

    eService.execute(new MyThread(countDownLatch, "A"));
    eService.execute(new MyThread(CountDownLatch2, "B"));


    try {
      countDownLatch.await();
      CountDownLatch2.await();

    } catch (InterruptedException exc) {
      System.out.println(exc);
    }
      finally{

        eService.shutdown(); // This is the method we use to avoid memory Leaks.
        // eService.shutdownNow(); // -do-
       }
  }
}

class MyThread implements Runnable {
  String name;

  CountDownLatch latch;

  MyThread(CountDownLatch c, String n) {
    latch = c;
    name = n;
    new Thread(this);
  }

  public void run() {
    for (int i = 0; i < 5; i++) {
      latch.countDown();
    }
  }
}

如果您忘记关闭,则会发生内存泄漏,它也像JVM通常不会关闭的流,因为默认的Executors不会创建守护程序线程。

答案 3 :(得分:0)

只是一个建议,但如果你每秒创建30条消息并且计算机(甚至并行)需要更长时间来处理这30条消息而不是1秒,那么你提交的任务队列将无法控制地增长。如果队列大小大于设定的数量并等待一下,则应确保没有提交任务。每个Message对象都使用内存,我认为这可能是你的问题。 cachedthreadpool无法解决此问题。

您可以通过打印出队列大小来完成测试。不知道这是否已经解决了......