1. 程式人生 > >由初始化執行緒池引發的NoClassDefFoundError 異常分析

由初始化執行緒池引發的NoClassDefFoundError 異常分析


今天說的異常是一個很不常見的異常,至少我不經常見到這個異常。
首先先看下NoClassDefFoundError官方定義 :

Java Virtual Machine is not able to find a particular class at runtime which was available at compile time. If a class was present during compile time but not available in java classpath during runtime.
Java 虛擬機器無法在執行時找到一個在編譯時可用的特定類。如果在編譯時存在類, 但在執行時 java 類路徑中不可用。


最近做的一個專案,由同事到客戶方部署及應用,但是期間發生一個詭異的問題:同一套程式碼打出的jar包在一個公司執行時會有一個NoClassDefFoundError異常丟擲。起初看到這個異常,我們都認為是打得包或者依賴有問題。於是便重新打包部署,結果還是同樣的問題。異常資訊如下:



很詭異的問題,順著報的錯誤去繼續查詢原因,最後將問題定位到一個執行緒池工具類中,部分程式碼如下:

其中 DEFAULT_MAX_CONCURRENT 定義如下:

private static final int DEFAULT_MAX_CONCURRENT = Runtime.getRuntime().availableProcessors() * 2;

這個執行緒池工具類在本地以及測試環境和線上環境一直都執行的沒有問題,因為報錯的異常資訊指向了這個類。
考慮到在多個客戶部署的都是同一套程式碼,只有硬體配置可能不同,而我們執行緒池初始化時的核心執行緒數依賴於硬體CPU核數,所以便猜測初始化執行緒池出了問題,核心執行緒數可能比最大執行緒數還大。
於是便開始追蹤原始碼,一探究竟。

執行緒池初始化原始碼:

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


繼續往下看其初始化過程:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

上面程式碼可能看出,如果corePoolSize>maxPoolSize 則會丟擲:IllegalArgumentException 異常,但是這和我們問題壓根不一樣啊?線索到這裡就斷了,但是至少發現了程式碼的一處Bug。

於是又開始沉思這個NoClassDefFoundError 異常究竟是怎麼來的了,開啟Oracle 文件便開始全域性搜尋這個,果不其然,有了新的發現:
(文件地址:https://docs.oracle.com/javase/specs/jls/se7/html/jls-12.html#jls-12.4.2)

這裡意思是初始化過程時,如果這個類是用c去實現的,且初始化丟擲異常時,都會對外丟擲NoClassDefFoundError 異常,到了這裡就很明朗了,果然是初始化執行緒池搞錯了。
於是趕緊檢視客戶機器CPU核數來驗證自己的猜想,果不其然,CPU為8核處理器。趕緊改了程式碼重新打包部署,一切到這裡就結束了。

不過通過這次異常也學到了很多:
1,能不用硬編碼的應該堅決杜絕,少埋這種坑。
2,多查文件,多查官方文件。

由於博主能力有限,所以如果您有更多的見解還請留言告知,不勝感激。