不合理的使用线程池引发的内存泄漏问题

问题代码

 /**
     * CPU核数
     */
    private static final int AVAILABLE_PROCESSORS = Runtime.getRuntime().availableProcessors();
    /**
     * 获取自定义线程池
     * @return
     */
    private ThreadPoolExecutor getThreadPoolExecutor(){
        return new ThreadPoolExecutor(
                AVAILABLE_PROCESSORS, 3 * AVAILABLE_PROCESSORS,3, TimeUnit.SECONDS,
                new LinkedBlockingDeque<>());
    }


  /**
     * 企微应用更新流程数
     * @param params 参数列表
     * @return
     * @throws Exception
     */
    public Boolean updateFlowInfo(Map<String, Object> params) throws Exception {
        if(StringUtil.isEmpty(params) || StringUtil.isEmpty(params.get("myflowAgentId"))){
            return true;
        }
        Integer agentId = Integer.valueOf(String.valueOf(params.get("myflowAgentId")));
        String token = getToken(agentId);
        String url = WORKBENCH_DATA.replace("ACCESS_TOKEN", token);
        List<String> list = (List<String>) params.get("info");

        ThreadPoolExecutor threadPoolExecutor = this.getThreadPoolExecutor();
        StringBuffer sb = new StringBuffer();

       
        List<CompletableFuture<String>> futures = list.stream().map(item -> {
            return CompletableFuture
                    .supplyAsync(() -> {
                        return Http.doPost(url, null, item);
                    }, threadPoolExecutor)
                    .whenCompleteAsync((r, e) -> {
                        if (ObjectUtils.isNotEmpty(r)) {
                            JSONObject resultJson = JSON.parseObject(r);
                            if (!"ok".equals(resultJson.getString("errmsg"))) {
                                sb.append(resultJson.getString("errmsg")).append("  ");
                            }
                        }
                    })
                    .exceptionally(e -> {
                        String message = e.getMessage();
                        if (StrUtil.isNotEmpty(message)) {
                            sb.append("错误信息:").append(message.substring(message.indexOf(":") + 1)).append("  ");
                        }
                        return null;
                    });
        }).collect(Collectors.toList());
        //执行完所有任务
        CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()])).join();
        if(sb.length() > 0){
            log.error("updateFlowInfo发送数据异常信息:"+sb.toString());
        }
        log.error("updateFlowInfo发送数据完成");
        return true;
    }

复现/定位问题

  1. .本地启动,通过 visualVM 进行监控,频繁请求,发现线程数居高不下,一直不回收

        2. 通过查看线程,发现里面有非常多的线程池对象

         3.进行300次请求后,导出dump文件进行查看类实例个数可以看到线程池数量和上面线程所对应的 $Worker对象基本和请求数吻合,由此可以判定是由于 $Worker对象的存在,导致线程无法被回收

4.再通过MAT工具进行查看GCRoot,并排除软引用和虚引用

5.发现里面全是Thread对象,由于Thread 是 活着的,因此可作为GC ROOT ,所以才会看到 局部线程池 ThreadPoolExecutor 没有被释放

引用链路:Thread->Worker->ThreadPoolExecutor

6.通过查看源码,线程池的 execute()方法中,会判断当前工作线程是否小于核心线程,不小于则会去调用 addWorker()方法

addWorker方法中,会使用 new Worker(firstTask),将当前线程传入到worker对象中,从而形成强引用的关系链路

7.线程池中的核心线程不会被停止,它会处于 waitting 状态一直等待任务,这一点从 visualVM中的线程概览也能看出来,所以导致无法被回收,属于一个内存泄露问题

解决办法

  • 使用完线程池后,执行 shutdown()方法

  • 使用全局变量定义线程池,不使用局部变量 new ThreadPool (推荐)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值