ソースの視点は、スレッドプールによって引き起こされるメモリの問題が急増し-newFixedThreadPool

序文

メモリはそれを舞い上がる引き起こす可能性がアンバウンド形式のキューのスレッドプールを使用しますか?インタビュアーは、多くの場合、すべての人の理解を深めことを期待して、メモリの問題newFixedThreadPoolスレッドプールがリードを高騰分析するために、この記事では、ソースコードに基づいて行われます、この質問をします。

メモリは、問題を再現するために急騰しました

サンプルコード

ExecutorService executor = Executors.newFixedThreadPool(10);
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            executor.execute(() -> {
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    //do nothing
                }
            });
        }
复制代码

設定パラメータのJVM

-Xmx8m -Xms8m:IDE JVMパラメータを指定しました:

結果

OOMがスローされます上記のコードを実行します。

JVMのOOMの問題は 、一般的に あまりにも多くのオブジェクトを作成し 、一方、 GCのゴミが リードを取り戻すには遅すぎ、そして何が理由である OOMスレッドプールにつながる こと?気分で新世界を発見、私たちは、コードがあまりにも多くのオブジェクトを作成するためのインスタンスを見つけるために見て、ビューのソースポイントから問題を分析します。

スレッドプールのソースコード解析

上記のコード例に、newFixedThreadPool及び実行方法。まずは、ソースコードnewFixedThreadPool方法を見てみましょう

newFixedThreadPoolソース

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
复制代码

ソース・コード・セグメントとの組み合わせスレッドプールの特性は、我々が知っていることができnewFixedThreadPoolを

  • スレッドcoreSizeスレッドmaximumPoolSizeの最大数のコア番号サイズは、nthreadsの値です。
  • アイドル時間が0である、すなわちkeepAliveTimeが0
  • ブロッキングキューは参照せずに構築されてLinkedBlockingQueue

スレッドプールは、私がこの記事を見ることができ、友人の特性の非常に明確な理解ではありません不可欠インタビュー:Javaはスレッドプールを解析します

次に、我々はメソッドの実装を見ては、スレッドプールのソースを実行します。

実行メソッド実行スレッドプール源

そして、関連するソースは、実行に説明し、次のように:

 public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {   //步骤一:判断当前正在工作的线程是否比核心线程数量小
            if (addWorker(command, true))    // 以核心线程的身份,添加到工作集合
                return;
            c = ctl.get();
        }
         //步骤二:不满足步骤一,线程池还在RUNNING状态,阻塞队列也没满的情况下,把执行任务添加到阻塞队列workQueue。
        if (isRunning(c) && workQueue.offer(command)) {  
            int recheck = ctl.get();
            //来个double check ,检查线程池是否突然被关闭
            if (! isRunning(recheck) && remove(command))  
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        //步骤三:如果阻塞队列也满了,执行任务以非核心线程的身份,添加到工作集合
        else if (!addWorker(command, false))
            reject(command);
    }
复制代码

上記のコードを見ると、我々はそれを見つけることができますaddWorkerとworkQueue.offer(コマンド)オブジェクトを作成することがあります。その後、我々は最初のaddWorker方法を分析します。

addWorkerソースコード解析

ソース及びaddWorker説明関連

private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            //获取当前线程池的状态
            int rs = runStateOf(c);

            //如果线程池状态是STOP,TIDYING,TERMINATED状态的话,则会返回false。
            // 如果现在状态是SHUTDOWN,但是firstTask不为空或者workQueue为空的话,那么直接返回false
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;
           //自旋
            for (;;) {
                //获取当前工作线程的数量
                int wc = workerCountOf(c);
                //判断线程数量是否符合要求,如果要创建的是核心工作线程,判断当前工作线程数量是否已经超过coreSize,
               // 如果要创建的是非核心线程,判断当前工作线程数量是否超过maximumPoolSize,是的话就返回false
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
               //如果线程数量符合要求,就通过CAS算法,将WorkerCount加1,成功就跳出retry自旋
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)
                    continue retry;
                  retry inner loop
            }
        }
        //线程启动标志
        boolean workerStarted = false;
        //线程添加进集合workers标志
        boolean workerAdded = false;
        Worker w = null;
        try {
            //由(Runnable 构造Worker对象
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                //获取线程池的重入锁
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                   //获取线程池状态
                    int rs = runStateOf(ctl.get());
                    //如果状态满足,将Worker对象添加到workers集合
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) 
                            throw new IllegalThreadStateException();
                        workers.add(w);
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
               //启动Worker中的线程开始执行任务
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            //线程启动失败,执行addWorkerFailed方法
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }
复制代码

addWorker実行プロセス

おそらく決定OKかどうかのスレッドプールの状態を、OKならば、現在の仕事にスレッド数を決定するには(coreSize / maximumPoolSize未満)を満たし、そうでない場合、追加しない会った場合は、タスクの労働者の作業セットに追加されます,,そして、スレッドの実行を開始します。

労働者のどのような種類を見てください:

    /**
     * Set containing all worker threads in pool. Accessed only when
     * holding mainLock.
     */
    private final HashSet<Worker> workers = new HashSet<Worker>();
复制代码

労働者coreSize / maximumPoolSizeを制御HashSetのコレクション、で、その後、addWorker方法は、OOMの原因は?結合コード例デモ、coreSize = maximumPoolSize = 10、10以上の場合、労働者に追加されていないので、サージnewFixedThreadPoolメモリの原因ではありませんだから、問題は方法のworkQueue.offer(コマンド)をある必要があります。我々は、実行を実行するフローチャートを描くものを、全体のプロセスを明確にします。

スレッドプールの実行方法は、プロセスを実行します

上記の実行とaddWorkソースコードの解析によると、我々はプロセスの外に絵を入れて:

  • タスクコマンドを送信すると、スレッドプールのコアの生存中のスレッドの数がcorePoolSize、addWorker方法が提出したコアミッションを処理するためのスレッドプールのスレッドを作成呼び出すスレッドの数よりも少ないです。
  • スレッドプール内のスレッドのコア数がいっぱいになっている場合は、そのスレッドの数に等しいcorePoolSize、提出新しいタスクは、ワークキューは、実行のためにキューに入れられたタスクキューに入れられますされています。
  • 同じcorePoolSizeを生き延び、およびタスクキューのワークキューがいっぱいだったスレッドプール内のスレッドの数、スレッドの数は、そのスレッドの最大数がいっぱいであるかどうかを、maximumPoolSizeかどうかを判断する場合には、届かない場合は、提出した非コアタスクを実行するスレッドを作成します。
  • スレッドの現在の数がmaximumPoolSizeに達し、だけでなく、新しいタスクが来た場合、ポリシー処理の使用を指示することを拒否しました。

実行フローを実行読んだ後、私は推測する、高騰メモリの問題があるワークキューがいっぱいアップ。次に、キューソースコード解析をブロック、メモリの問題が高騰発表しました。

ブロッキングキューソースコード解析

戻るnewFixedThreadPoolコンストラクタブロッキングキューはLinkedBlockingQueueを発見され、それはありません 引数のLinkedBlockingQueueキュー OK、我々は分析LinkedBlockingQueueソースを指示します。

LinkedBlockingQueue类图

これは、図のクラスから見ることができます。

  • キュー要素の数を記録するために、COUNT LinkedBlockingQueue 2のノードを有するリンクされたリストが実装されているワンウェイは、最初と最後のノードを記憶するために使用され、0原子変数の初期値があります。
  • takeLockつのみのスレッドがキューの先頭から得ることができる要素を制御するために使用されるエンキューおよびデキューを制御するための2つのReentrantLockのそれぞれの原子要素の別の例は、他のスレッドが同時にのみ制御され、putLockを待たなければなりませんスレッドは、キューの末尾に要素を追加し、他のスレッドが待機する必要があり、ロックを取得することができます。
  • また、notEmptyとnotFull条件は、彼らがチームにチームとは、実際には、これは生産者、消費者のモデルであるとき、スレッドを格納するために使用条件内部キューがブロックされている必要があり、可変です。

いいえ、参照コンストラクタLinkedBlockingQueueありません

    public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }
    public LinkedBlockingQueue(int capacity) {
        if (capacity <= 0) throw new IllegalArgumentException();
        this.capacity = capacity;
        last = head = new Node<E>(null);
    }
复制代码

引数なしのコンストラクタLinkedBlockingQueue、デフォルトコンストラクタは、**はInteger.MAX_VALUE(あまり)**リストは、あなたがプロセスを実行思い出し、ここを参照してください、キューがブロックされていないことは、このキューの寛大さを、満たされていませんすべてのブロックされたタスクは、コマンドで閉じます。メモリは、下の高騰友達に取得問題ではありません。

オファーのLinkedBlockingQueue機能

スレッドプール、提供の方法により、キューに挿入された、我々はそれを操作するキューサオを遮断LinkedBlockingQueueのオファーを見て

public boolean offer(E e) {
        //为空元素则抛出空指针异常
        if (e == null) throw new NullPointerException();
        final AtomicInteger count = this.count;
        //如采当前队列满则丢弃将要放入的元素, 然后返回false 
        if (count.get() == capacity)
            return false;
        int c = -1;
        //构造新节点,获取putLock独占锁
        Node<E> node = new Node<E>(e);
        final ReentrantLock putLock = this.putLock;
        putLock.lock();
        try {
            //如采队列不满则进队列,并递增元素计数 
            if (count.get() < capacity) {
                enqueue(node);
                c = count.getAndIncrement();
                //新元素入队后队列还有空闲空间,则
                唤醒 notFull 的条件队列中一条阻塞线程
                if (c + 1 < capacity)
                    notFull.signal();
            }
        } finally {
            //释放锁 
            putLock.unlock();
        }
        if (c == 0)
            signalNotEmpty();
        return c >= 0;
    }
复制代码

操作オファーアイドルキューがある場合にキューがヘキシル現在の要素を破棄され、フルである場合は、キューの末尾に挿入要素は、その後、その後、falseを返し、真の成功したリターンを挿入します。E要素がnullの場合はNUL!のPointerException例外がスローされます。さらに、この方法は、ノンブロッキングです。

メモリは、結果が発表された問題を急騰しました

newFixedThreadPoolスレッドプールコアスレッドが固定されている近使用する、無制限のブロッキングキューLinkedBlockingQueueをタスクを実行するために比較的長い時間が、何のリリースが存在しない場合にコアスレッドが、ブロッキングキューに、タスクチームを使用する場合は、につながるキューをブロックし蓄積することが、より多くの作業機械のメモリ使用量につながる、高騰保たれ、 JVM OOMを引き起こします。

リファレンスと感謝

個人公開番号

  • あなたが少年を学ぶの愛であれば、私は議論を一緒に学び、公共の数に焦点を当てることができます。
  • あなたは、この記事では、適切な場所ではないことを感じるコメントすることができた場合、私はまた私をチャットプライベート、パブリックの数、懸念、我々はカザフスタンを進める一緒に検討することができます。

おすすめ

転載: juejin.im/post/5d6a57eee51d4561e721df30