线上CompletableFuture不恰当使用导致的oom

1,257 阅读2分钟

前言

线上容器6g内存,然后vm主要配置是-Xms4800m -Xmx4800m 同事需要全量跑数据,需要异步线程池。 于是,我就给对方来了个简单的线程池。

大致代码

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(4, 4, 60, TimeUnit.SECONDS, new LinkedBlockingDeque<>());
        List<CompletableFuture<Void>> taskList = new ArrayList<>();
        for (int i = 0; i < 8000; i++) {
            int finalI = i;
            CompletableFuture<Void> task = CompletableFuture.runAsync(()->{
                try {
                    TimeUnit.MILLISECONDS.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("task "+ finalI +" finish");
            },threadPoolExecutor);
            taskList.add(task);
        }
        CompletableFuture.allOf(taskList.toArray(new CompletableFuture[taskList.size()])).join();

在全量跑数据,线上就报错,提示容器发生oom

分析

找到对应机器的gc图标分析 113a47b6a11705a7663bf40a427a6c3.png

在对应的时间点,我们可以看到,我们在两次全量跑数据的时候,明显老年代的内存有变大。 但是为什么是容器提示oom,而不是应用程序提示oom呢

解决方案

如何处理oom

当前还没有进行深层次的优化。由于是后台项目,使用并不多,所以只有4台机器。 当前解决方案: 8000分批次处理,每次运行32个任务,也不是特别优雅,但是也解决了任务.

ExecutorService executorService = new ThreadPoolExecutor(32,32,60,TimeUnit.SECONDS,new LinkedBlockingDeque<>());
        List<CompletableFuture<Void>> taskList = new ArrayList<>();
        for (int i = 0;i<200000;i++){
            int finalI = i;
            CompletableFuture<Void> completableFuture = CompletableFuture.supplyAsync(()->{
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("task "+ finalI +"finish");
                return null;
            },executorService);
            taskList.add(completableFuture);

            if(taskList.size()>32){
                CompletableFuture.allOf(taskList.toArray(new CompletableFuture[taskList.size()])).join();
                taskList.clear();
            }
        }

        if(taskList.size()>0){
            CompletableFuture.allOf(taskList.toArray(new CompletableFuture[taskList.size()])).join();
            taskList.clear();
        }

为什么线上应用程序没有错误日志,而是容器oom

这就是我们在使用CompletableFuture不规范导致的。 我们来测试段简单的代码,vm参数设置小一点 -Xms10m -Xmx10m

 // 8000任务
        List<Integer> policyList = IntStream.range(0, 8000).boxed().collect(Collectors.toList());
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(4, 4, 60, TimeUnit.SECONDS, new LinkedBlockingDeque<>());
        List<CompletableFuture<Void>> taskList = new ArrayList<>();
        for (Integer policy : policyList) {
            CompletableFuture<Void> task = CompletableFuture.supplyAsync(()->{
                // 我也不确定,做了什么
                // 这边业务逻辑,一部分是http请求,一部分是sql查询插入,等等
                byte[] byteArr = new byte[1024 * 1024 * 4];
                System.out.println(policy + "处理完成");
                return null;
            },threadPoolExecutor);
            taskList.add(task);
        }

运行一段事件,应用程序不继续输出处理完成了,也没有报错。 注意:1641309143(1).png,不是Exception 解决方案有两种 1.在使用CompletableFuture需要用whenComplete处理,取捕获throwable 2.在执行异步方法的外部用try catch throwable来处理。

后续优化方案

1.通过异步调用http方式,通过负载均衡,将压力到其他机器上 2.mq方式,让集群处理。 ​