由初始化线程池引发的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,多查文档,多查官方文档。

由于博主能力有限,所以如果您有更多的见解还请留言告知,不胜感激。

posted @ 2018-09-27 20:04  一枝花算不算浪漫  阅读(1874)  评论(0编辑  收藏  举报