关于ThreadPool抛出OOM问题

关于ThreadPool抛出OOM问题

案例

最近在学习Java调优,有个案例是ThraadPool导致OOM,在不了解线程池的情况很难看出问题来。
代码片.

package com.example.demo;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class TestThreadPool {
    public static void main(String[] arr) {
        ExecutorService executorService = new ThreadPoolExecutor(1, 2,
                0, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(1000000), 
                new ThreadPoolExecutor.DiscardPolicy());

        for (; ; ) {
            Person person = new Person();
            executorService.execute(() -> {
                person.doingSomething();

            });
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

    static class Person {
        public String name;
        public Integer sex;
        public String age;

        public void doingSomething() {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

这段代码很简单,创建一个线程很少,任务队列足够大的线程池,然后频繁的给线程池加执行任务,
很显然,在jvm内存设置的不够大的情况,抛出outOfMemory是迟早的事情,只是这个时间有点长。

不信执行 java -Xms20M -Xmx20M -XX:+PrintGC com.example.demo.TestThreadPool 试试
为了快速试错,将堆大小改成20M。

下面花了4个小时终于等到错误结果了:
在这里插入图片描述

为什么会OOM

我们来分析一下,首先线程池执行execute时,并不是马上执行任务,它是先把它插入线程池里面的队列,然后排队由线程池的线程执行。你可以把它想象成一个蓄水池,需要执行的任务是水,池子一边进水,一边放水,什么情况下会发生水的溢出呢?很简单,进水的速度比放水的速度还快时,水就会溢出。我们再来回到代码,上面的代码每100毫秒就会加一个执行任务,那么一秒钟的时间它加入任务数是1000/100=10个,然后这个线程池最多有两个执行线程,每个线程执行的时间需要3秒,那它1秒钟能释放的任务数是2*1000/3000=2/3个,10>2/3,进来的速度明显比释放的快多了,所以它任务队列是一直增长的,在任务里面需要使用的对象,jvm是不会把它回收的,所以上面的例子中,创建person实例而不被gc回收的数量会越来越多,最终会把堆内存撑爆。

那么要怎么样才能解决这个问题呢?这个没有100%可以保证解决的办法,但是可以从两个方面去处理,要么减少加入的任务数量(但是很多程序它的压力就是那么大的没法优化,可能只能想其他办法,通过负载均衡加多个更多的服务),要么增加线程数量(线程数也不是越多越好的),那如果采用增加线程的方式,那要加多少线程才够呢?这个问题就要需要用到小学数学知识了,任务增加的速率为1000/100=10,一个线程处理的任务的速率是1*1000/3000=1/3,这个线程池一共至少需要10/(1/3)=30个线程,才能消化掉这个创建任务速度。

发布了6 篇原创文章 · 获赞 7 · 访问量 244

猜你喜欢

转载自blog.csdn.net/liuweijin_hot/article/details/105065812