有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

使用执行器的java内存泄漏。newFixedThreadPool()

我正在使用Spring3。1在独立环境上

(这个问题与Spring无关。它在独立环境中的行为也一样。)

我实现了一个侦听器,它接收来自主题的消息。消息速率非常高(约为20/30 m/s)

某些消息可能需要比其他消息更多的处理时间

监听器处理同一个实例,这意味着如果一条消息被处理的时间太长,它会严重影响我们的性能

我们考虑使用自己的对象池,而不是使用同一个侦听器实例,但后来我发现了执行器(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) 个答案

  1. # 1 楼答案

    我相信您遇到的问题是threadPool没有释放其资源。你需要打电话给threadPool。完成提交或执行后关闭()。这将等到任务完成后再终止线程,然后可以对线程进行垃圾收集

    从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. # 2 楼答案

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

    你可以通过打印你的队列大小来测试这一点。不知道这个问题是否已经解决了

  3. # 3 楼答案

    在使用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通常不关闭的流,因为默认执行器不会创建守护进程线程

  4. # 4 楼答案

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

    注意:

    • 线程数不应显式影响堆内存。下一步复习可能对你有用。所以线程需要堆栈内存,而不是堆
    • 一般来说,由于GC works算法,为不重的对象创建缓存不是一个好主意 <> Li >我认为您应该考虑cached thread pool或根据服务器硬件校准TracePoLosig。<李>