執行緒池OOM異常

eluanshi12發表於2020-10-23

現象

MAC
現象
unable to creat new native thread
window
測試前 在啟動測試類之前先將JVM記憶體調整小一點,不然很容易將電腦跑出問題
在idea裡:Run -> Edit Configurations VM options修改成-Xms10M -Xmx10M
(-Xms10M Java Heap記憶體初始化值 -Xmx10M Java Heap記憶體最大值)
window執行情況

Exception in thread “main” java.lang.OutOfMemoryError: Java heap space。
程式碼

public static void main(String[] args) throws InterruptedException {
        AtomicInteger i = new AtomicInteger(0);
        for (; ; ) {
            Executors.newFixedThreadPool(10).submit(() -> System.out.println(i.getAndIncrement()));
            Thread.sleep(4);
        }
    }

迴圈建立執行緒池 而並沒有關閉執行緒池,導致執行緒池的例項會一直存在

原始碼

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
}
public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE); //  MAX_VALUE = 0x7fffffff
}

   public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

原因分析

Java常見的GC Root
Java 進行GC的時候會從GC root進行可達性判斷,常見的GC Root有如下:

  1. 通過System Class Loader或者Boot Class Loader載入的class物件,通過自定義類載入器載入的class不一定是GC Root
  2. 處於啟用狀態的執行緒
  3. 棧中的物件
  4. JNI棧中的物件
  5. JNI中的全域性物件
  6. 正在被用於同步的各種鎖物件
  7. JVM自身持有的物件,比如系統類載入器等。
    在調查記憶體洩漏原因的時候可以根據GC Root來推導

當執行一個Runnable時,會先建立一個ThreadPoolExecutor中的內部類Worker物件,將這個Runnable物件作為Worker物件的一個成員變數

 Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }
 private volatile ThreadFactory threadFactory;       
 public ThreadFactory getThreadFactory() {
        return threadFactory;
    }       

所以 當執行緒在執行的時候引用關係如下ThreadPoolExecutor->Worker->thread
一個執行的執行緒是作為GC ROOT的,不會被GC; 所以要主動shutdown

newSingleThreadExecutor不會有此問題,因為FinalizableDelegatedExecutorService 重寫了finalize函式,也就是說這個類會在被GC回收之前,先執行執行緒池的shutdown方法。

java.util.concurrent.Executors

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

static class FinalizableDelegatedExecutorService
        extends DelegatedExecutorService {
        FinalizableDelegatedExecutorService(ExecutorService executor) {
            super(executor);
        }
        protected void finalize() {
            super.shutdown();
        }
    }

執行緒池的使用注意事項

ThreadPoolExecutor-執行緒池如何保證執行緒不被銷燬

相關文章