7

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

(此问题与 Spring 无关。它在独立环境中的行为也相同。)

我实现了一个从主题接收消息的侦听器。消息速率非常高(大约 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 大小数字“玩”一点。仍然有大量内存使用:

在此处输入图像描述

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

执行 GB 后的 jconsole

jconsole 整体视图

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

头部转储

谢谢,雷。

4

4 回答 4

4

我相信你遇到的问题是线程池没有释放它的资源。完成提交或执行后,您需要调用 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() 和关闭选项。

于 2017-05-10T11:31:04.943 回答
2

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

一些注意事项:

  • 线程数不应显式影响堆内存。复习下一个java 内存结构可能对您有用。所以线程需要堆栈内存而不是堆。
  • 一般来说,为不重的对象创建缓存不是一个好主意,因为 GC 工作算法。
  • 另外我认为您应该考虑缓存线程池或根据服务器硬件校准 ThreadPoolSize 。
于 2012-12-17T09:12:11.873 回答
0

使用 ExecutorService 后,您需要停止线程池,因为它是相同的资源,如文件或数据库或其他任何需要显式释放的资源。ExecutorService 有方法 shutdown() 和 shutdownNow() 可以在 finally 块中用于垃圾收集。

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 不会创建守护线程。

于 2012-12-17T09:12:23.677 回答
0

只是一个建议,但如果您每秒创建 30 条消息,并且计算机(即使并行处理)处理这 30 条消息的时间比 1 秒长,那么您提交的任务队列将无法控制地增长。如果队列大小大于设定的数量,您应该确保没有提交任何任务并稍等片刻。每个 Message 对象都使用内存,我认为这可能是您的问题。缓存线程池不能解决这个问题。

你可以很简单地通过打印出你的队列大小来测试它。不知道这是否已经解决了......

于 2017-01-24T08:09:06.980 回答