為什么Java線程池會導致CPU占用100%?如何排查和解決這個問題?

Java 線程池導致CPU占用100%的原因及排查方法

近日,我們在線上服務中發現了一個容器的cpu使用率突然達到100%,為了保障系統的穩定性,我們首先將該容器下線,停止新的流量進入。然而,即使沒有新的請求,容器中的java進程cpu使用率依然居高不下。隨后,我們通過top命令檢查各個線程的使用情況,發現高cpu占用的線程是由線程池創建的。

這引發了我們的疑問:既然容器已經下線,為何線程池還會持續執行任務?接著,我們使用jstack命令查看各個線程的情況,發現線程池中有64個線程,其中40個線程處于等待新任務的狀態(waiting),而另外24個線程則處于可運行狀態(runnable)。具體的可運行線程堆棧信息如下:

"pool-1-thread-1" prio=10 tid=0x00007f9f5c01e800 nid=0x1a runnable [0x00007f9f54527000]    java.lang.Thread.State: RUNNABLE         at java.base/java.util.concurrent.LinkedBlockingQueue.poll(LinkedBlockingQueue.java:464)         at java.base/java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1056)         at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1118)         at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)         at java.base/java.lang.Thread.run(Thread.java:834)

從堆棧信息可以看出,這些可運行的線程正在嘗試獲取任務,但實際上并沒有線程在執行任務。這非常奇怪,因為線程池中的線程在查詢是否有新任務的操作應該非常快且短暫。理論上,線程池中的線程應該要么獲取任務并執行任務中的代碼,要么因為沒有任務而陷入等待狀態(waiting),只有極少數情況下會處于可運行狀態并試圖獲取任務。不可能有40%的線程都處于這種狀態。

為了進一步了解情況,我們生成并分析了Java進程的火焰圖,發現CPU主要消耗在LinkedBlockingQueue.poll方法上,這與jstack的信息一致。我們開始懷疑可能是線程池的配置問題,導致線程不斷輪詢任務隊列而無法進入等待狀態(waiting)。經確認,我們的線程池配置了maxPoolSize為64,corePoolSize為16,當前線程池中的線程數量達到了最大值64。線程池還配置了keepAliveTime參數為60秒,這樣如果線程在60秒內沒有獲取到任務,它會等待60秒鐘,如果仍然沒有任務,線程將終止直到線程數量減少到corePoolSize。我們使用Arthas查看LinkedBlockingQueue.poll的調用情況,但并沒有發現它被頻繁調用。

在這一步,我們的思路陷入了停滯,目前只能懷疑LinkedBlockingQueue.poll方法存在某些bug,導致獲取不到任務時會陷入死循環。但考慮到這是JDK內部的代碼,這種明顯的bug應該是不太可能的。我們使用的JDK版本是zulu-17。是否有其他人遇到過類似的問題,提供一些提示和方向呢?

立即學習Java免費學習筆記(深入)”;

根據現有的信息,我們可以推測在調用poll()方法時需要獲取takeLock。當大量線程同時調用poll()時,鎖競爭不可避免。在這個過程中,沒有獲取到鎖的線程會進行自旋等待操作,從而導致CPU占用率升高(仍然處于RUNNABLE狀態),這也會使keepAliveTime失效,無法回收線程。

我們可以查看LinkedBlockingQueue.poll方法的實現:

public E poll(long timeout, TimeUnit unit) throws InterruptedException {     final E x;     final int c;     long nanos = unit.toNanos(timeout);     final AtomicInteger count = this.count;     final ReentrantLock takeLock = this.takeLock;     takeLock.lockInterruptibly();     try {         while (count.get() == 0) {             if (nanos  1)             notEmpty.signal();     } finally {         takeLock.unlock();     }     if (c == capacity)         signalNotFull();     return x; }

基于以上分析,我們可以嘗試以下幾種解決方案:

  1. 調整線程池大小:將maxPoolSize從64調整到32,這樣可以減少鎖競爭。
  2. 更換隊列類型:嘗試使用ArrayBlockingQueue,這種隊列的鎖競爭開銷較小,雖然吞吐量可能會有所下降。
  3. 排查代碼:檢查是否有提交了死循環任務,導致線程池中的線程無法正常結束。
  4. 更換JDK:嘗試使用其他版本的JDK,看是否能解決問題。
  5. 檢查容器資源分配:確保容器的資源分配充足,避免因資源不足導致的性能問題。

為什么Java線程池會導致CPU占用100%?如何排查和解決這個問題?

以上就是<a

? 版權聲明
THE END
喜歡就支持一下吧
點贊13 分享