threadpool的oom(out of memory)问题分析 您所在的位置:网站首页 java线程池oom threadpool的oom(out of memory)问题分析

threadpool的oom(out of memory)问题分析

2023-09-04 12:23| 来源: 网络整理| 查看: 265

线程池的声明需要手动进行

《阿里巴巴 Java 开发手册》中并不推荐使用Java 中的 Executors 类里的快捷的工具方法来快速创建线程池,更应该根据实际的需求等,通过手动 new ThreadPoolExecutor 来创建线程池。否则可能会出现oom问题

接下来我们开始使用Executors.newFixedThreadPool,Executors.newCachedThreadPool来进行测试

一:创建一个打印线程池状态的简易方法 private void printStats(ThreadPoolExecutor threadPool) { //每秒钟打印一下线程池的状态 Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> { log.info("========================="); log.info("Pool Size: {}", threadPool.getPoolSize()); log.info("Active Threads: {}", threadPool.getActiveCount()); log.info("Number of Tasks Completed: {}", threadPool.getCompletedTaskCount()); log.info("Number of Tasks in Queue: {}", threadPool.getQueue().size()); log.info("========================="); }, 0, 1, TimeUnit.SECONDS); } 二:Executors.newFixedThreadPool @GetMapping("oom1") public void oom1() throws InterruptedException { ThreadPoolExecutor threadPool = (ThreadPoolExecutor) Executors.newFixedThreadPool(1); printStats(threadPool); for (int i = 0; i String payload = IntStream.rangeClosed(1, 1000000) .mapToObj(__ -> "a") .collect(Collectors.joining("")) + UUID.randomUUID().toString(); try { TimeUnit.HOURS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } log.info(payload); }); } threadPool.shutdown(); threadPool.awaitTermination(1, TimeUnit.HOURS); }

代码执行后不就就会抛出以下异常:

Exception in thread "http-nio-45678-ClientPoller" java.lang.OutOfMemoryError: GC overhead limit exceeded

这是我们查看日志:

[20:24:36.357] [pool-7-thread-1] [INFO ] [o.g.t.c.t.t.ThreadPoolOOMController:26 ] - ========================= [20:24:36.358] [pool-7-thread-1] [INFO ] [o.g.t.c.t.t.ThreadPoolOOMController:27 ] - Pool Size: 1 [20:24:36.358] [pool-7-thread-1] [INFO ] [o.g.t.c.t.t.ThreadPoolOOMController:28 ] - Active Threads: 1 [20:24:36.358] [pool-7-thread-1] [INFO ] [o.g.t.c.t.t.ThreadPoolOOMController:29 ] - Number of Tasks Completed: 0 [20:24:36.358] [pool-7-thread-1] [INFO ] [o.g.t.c.t.t.ThreadPoolOOMController:30 ] - Number of Tasks in Queue: 99999999 [20:24:36.358] [pool-7-thread-1] [INFO ] [o.g.t.c.t.t.ThreadPoolOOMController:31 ] - =========================

我们查看源码发现:

public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()); } */ public LinkedBlockingQueue() { this(Integer.MAX_VALUE); }

LinkedBlockingQueue的长度为Integer.MAX_VALUE,虽然使用 newFixedThreadPool 可以把工作线程控制在固定的数量上,但任务队列是无界的。如果任务较多并且执行较慢的话,队列可能会快速积压,撑爆内存导致 OOM。

三:newCachedThreadPool

我们将newFixedThreadPool改用为newCachedThreadPool

没一会儿就抛出:

{ "timestamp": "2020-03-30T12:27:34.201+0000", "status": 500, "error": "Internal Server Error", "message": "unable to create new native thread", "path": "/threadpooloom/oom2" }

我们查看源码:

public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue()); }

工作队列 SynchronousQueue 是一个没有存储空间的阻塞队列,这意味着,只要有请求到来,就必须找到一条工作线程来处理,如果当前没有空闲的线程就再创建一条新的。在任务耗时长且任新增迅猛,很容易就造成oom。

现实中的场景有:如果第三方短信服务平台出现问题,接口处理时间剧增,但是业务系统的短信通知请求不停发送,如果使用上述两种Executors自带的快速生成线程池的function,则很可能造成生产事故。

四:总结

我们需要根据自己的场景、并发情况来评估线程池的几个核心参数,包括核心线程数、最大线程数、线程回收策略、工作队列的类型,以及拒绝策略,确保线程池的工作行为符合需求,一般都需要设置有界的工作队列和可控的线程数。任何时候,都应该为自定义线程池指定有意义的名称,以方便排查问题。当出现线程数量暴增、线程死锁、线程占用大量 CPU、线程执行出现异常等问题时,我们往往会抓取线程栈。此时,有意义的线程名称,就可以方便我们定位问题。

五:扩展

线程池不会被回收,即使是自定义线程池,核心线程是不会回收的,如果代码里面每次都是创建新的线程池,如果每次需要10个线程,刚好是核心线程数,因此每次请求都会创建10个核心线程数的线程池,请求次数多了后,很快就回OOM。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有