【问题标题】:OutOfMemoryError: unable to create new native thread using ExecutorServiceOutOfMemoryError:无法使用 ExecutorService 创建新的本机线程
【发布时间】:2018-05-10 22:03:06
【问题描述】:

我在一夜之间启动了我的实例,看看它是如何处理事情的,当我今天早上来的时候,我正面临一个

Exception in thread "pool-535-thread-7" java.lang.OutOfMemoryError: unable to create new native thread
    at java.lang.Thread.start0(Native Method)
    at java.lang.Thread.start(Thread.java:691)
    at java.util.concurrent.ThreadPoolExecutor.addWorker(ThreadPoolExecutor.java:943)
    at java.util.concurrent.ThreadPoolExecutor.processWorkerExit(ThreadPoolExecutor.java:992)[info] application - Connecting to server A
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
    at java.lang.Thread.run(Thread.java:722)

我的代码的目的很简单:每 5 分钟,我连接到一个远程服务器列表,发送一个请求(通过套接字),就是这样。

这是我的代码:

我的“cron”任务:

/** will create a new instance of ExecutorService every 5 minutes, loading all the websites in the database to check their status **/
/** Maybe that's where the problem is ? I need to empty (GC ?) this ExecutorService ? **/
Akka.system().scheduler().schedule(
    Duration.create(0, TimeUnit.MILLISECONDS), // Initial delay 0 milliseconds
    Duration.create(5, TimeUnit.MINUTES),     // Frequency 5 minutes
    new Runnable() {
        public void run() {
            // We get the list of websites to check
            Query<Website> query = Ebean.createQuery(Website.class, "WHERE disabled = false AND removed IS NULL");
            query.order("created ASC");
            List<Website> websites = query.findList(); // Can be 1, 10, 100, 1000. In my test case, I had only 9 websites.

            ExecutorService executor = Executors.newFixedThreadPool(NTHREDS);
            for (Website website : websites) {
                CheckWebsite task = new CheckWebsite(website);
                executor.execute(task);
            }

            // This will make the executor accept no new threads
            // and finish all existing threads in the queue
            executor.shutdown();
        }
    },
    Akka.system().dispatcher()
);

我的 CheckWebsite 类:

public class CheckWebsite implements Runnable {
    private Website website;

    public CheckWebsite(Website website) {
        this.website = website;
    }

    @Override
    public void run() {
        WebsiteLog log = website.checkState(); // This is where the request is made, I copy paste the code just after
        if (log == null) {
            Logger.error("OHOH, WebsiteLog should not be null for website.checkState() in CheckWebsite class :s");
            return;
        }

        try {
            log.save();
       catch (Exception e) {
            Logger.info ("An error occured :/");
            Logger.info(e.getMessage());
            e.printStackTrace();
        }
    }
}

Website.class 中我的checkState() 方法:

public WebsiteLog checkState() {
    // Since I use Socket and the connection can hang indefinitely, I use an other ExecutorService in order to limit the time spent
    // The duration is defined via Connector.timeout, Which will be the next code.

    ExecutorService executor = Executors.newFixedThreadPool(1);

    Connector connector = new Connector(this);
    try {
        final long startTime = System.nanoTime();

        Future<String> future = executor.submit(connector);
        String response = future.get(Connector.timeout, TimeUnit.MILLISECONDS);

        long duration = System.nanoTime() - startTime;

        return PlatformLog.getLastOccurence(this, response, ((int) duration/ 1000000));
    }
    catch (Exception e) {
        return PlatformLog.getLastOccurence(this, null, null);
    }
}

这是Connector.class。我在这里删除了无用的部分(如 Catches):

public class Connector implements Callable<String> {
    public final static int timeout = 2500; // WE use a timeout of 2.5s, which should be enough

    private Website website;

    public Connector(Website website) {
        this.website = website;
    }

    @Override
    public String call() throws Exception {
        Logger.info ("Connecting to " + website.getAddress() + ":" + website.getPort());
        Socket socket = new Socket();

        try {
            socket.connect(new InetSocketAddress(website.getIp(), website.getPort()), (timeout - 50));
            BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String response = input.readLine();
            socket.close();

            return response;
        }
        catch (Exception e) {
            e.printStackTrace();
            throw e;
        }
        finally {
            // I take the precaution to close the socket here in order to avoid a memory leak
            // But if the previous ExecutorService force the close of this thread before
            // I can't guarantee it will be closed :/
            if (socket != null && !socket.isClosed()) {
                socket.close();
            }
        }
    }
}

我是 Java 多线程的新手,所以我可能犯了大错。我怀疑某些领域可能是原因,但我缺乏知识需要我寻求您的帮助:)

总而言之,以下是潜力领域:

  1. 每 5 分钟创建一个新的 ExecutorService。也许我可以重复使用旧的?或者我是否需要在完成后关闭当前的(如果需要,如何?)。
  2. 事实上,我创建了一个ExecutorService,它将创建一个ExecutorService(在checkstate() 方法中)
  3. 如果运行时间过长,Connector 类可以(暴力地)被运行它的ExecutorService 停止,从而导致套接字未关闭(然后是内存泄漏)?

此外,如您所见,线程“pool-535-thread-7”发生了异常,这意味着它不会很快发生。

我将 last_occured 检查存储在数据库中,并创建日志条目(在 WebsiteLog 中),增量大约为 5 小时(因此,每 5 分钟,线程在大约 60 次调用后崩溃)。

更新:这是重新访问的 checkState 方法以包含关闭调用:

public PlatformLog checkState() {
    ExecutorService executor = Executors.newFixedThreadPool(1);

    Connector connector = new Connector(this);
    String response = null;
    Long duration = null;

    try {
        final long startTime = System.nanoTime();

        Future<String> future = executor.submit(connector);
        response = future.get(Connector.timeout, TimeUnit.MILLISECONDS);

        duration = System.nanoTime() - startTime;
    }
    catch (Exception e) {}

    executor.shutdown();
    if (duration != null) {
        return WebsiteLog.getLastOccurence(this, response, (duration.intValue()/ 1000000));
    }
    else {
        return WebsiteLog.getLastOccurence(this, response, null);
    }
}

【问题讨论】:

  • 如果您认为自己找到了解决办法,为什么不测试一下呢?将您的 cron 作业更改为每 1 分钟(或更少?),您可以在一小时内完成 60 个调用...
  • 这很聪明! :p 我会这样做的。 - 30 分钟后见;)(每 30 秒)

标签: java multithreading executorservice


【解决方案1】:

我不确定这是唯一的问题,但您在 checkState() 方法中创建了一个 ExecutorService,但您没有将其关闭。

根据Executors.newFixedThreadPool() 的JavaDocs:

池中的线程将存在直到它被明确关闭。

保持活动状态的线程将导致ExecutorService not to be garbage collected(它将代表您调用shutdown()。因此,每次调用它时您都会泄漏一个线程。

【讨论】:

  • 哦,是的,我忘了关闭 ExecutorService,谢谢。但是关于连接器,我该如何退出呢?我在尝试中返回,但在 catch/finally 中什么都没有。我也应该在这里做点什么吗?
  • @CyrilN。不,我觉得很好。您要么返回结果,要么抛出异常,这是正确的行为。如果不是它就不会编译!
  • 我更新了我的问题以显示checkState() 的重新访问版本。你觉得这样更好吗?顺便说一句,如果我在Connector.class 中抛出异常,我将永远不会去 finally 块正确(所以我永远不会关闭套接字)?
  • @CyrilN。另请注意,ExecutorService 不会强制线程停止。它可能会尝试中断线程,但您的代码不会查找中断状态。因此,在最坏的情况下,您的阻塞套接字调用将被中断,这将被您的异常代码捕获。您的 finally 子句仍将运行。
  • @CyrilN。唯一可以阻止执行finally 子句的是强制终止JVM。在所有其他情况中,finally 块将执行,包括如果您抛出异常。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-03-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多