StackOverflowError竟然会导致java.lang.NoClassDefFoundError:类加载机制导致的惨案

83 阅读2分钟

 现象

生产环境再一次发布后,突然大量告警,经过排查,发现出现大量接口报以下异常

{"timestamp":1750703779072,"status":500,"error":"Internal Server Error","exception":"java.lang.NoClassDefFoundError","message":"Could not initialize class java.sql.SQLException","path":"/xxx/xxx"}

系统日志

java.lang.NoClassDefFoundError: Could not initialize class java.sql.SQLException
    at com.alibaba.druid.pool.DruidPooledConnection.checkStateInternal(DruidPooledConnection.java:1157)
    at com.alibaba.druid.pool.DruidPooledConnection.checkState(DruidPooledConnection.java:1148)
    at com.alibaba.druid.pool.DruidPooledConnection.prepareStatement(DruidPooledConnection.java:336)
    at org.apache.ibatis.executor.statement.PreparedStatementHandler.instantiateStatement(PreparedStatementHandler.java:87)
    at org.apache.ibatis.executor.statement.BaseStatementHandler.prepare(BaseStatementHandler.java:88)
    at org.apache.ibatis.executor.statement.RoutingStatementHandler.prepare(RoutingStatementHandler.java:59)
    at org.apache.ibatis.executor.SimpleExecutor.prepareStatement(SimpleExecutor.java:85)
    at org.apache.ibatis.executor.SimpleExecutor.doQuery(SimpleExecutor.java:62)
    at org.apache.ibatis.executor.BaseExecutor.queryFromDatabase(BaseExecutor.java:324)
    at org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:156)
    at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:109)
    at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:83)
    at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:148)
    at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:141)
    at sun.reflect.GeneratedMethodAccessor270.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:434)
    at com.sun.proxy.$Proxy150.selectList(Unknown Source)
    at org.mybatis.spring.SqlSessionTemplate.selectList(SqlSessionTemplate.java:231)
    at org.apache.ibatis.binding.MapperMethod.executeForMany(MapperMethod.java:137)
    at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:75)
    at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:53)
    at com.sun.proxy.$Proxy309.queryList(Unknown Source)
    at com.custom.hsi.mm.uniondb.service.impl.IUnionCommonServiceImpl.getList(IUnionCommonServiceImpl.java:278)
    at com.custom.hsi.mm.uniondb.service.impl.IUnionCommonServiceImpl.getEntity(IUnionCommonServiceImpl.java:206)
    at com.alibaba.dubbo.common.bytecode.Wrapper116.invokeMethod(Wrapper116.java)
    at com.alibaba.dubbo.rpc.proxy.javassist.JavassistProxyFactory$1.doInvoke(JavassistProxyFactory.java:47)
    at com.alibaba.dubbo.rpc.proxy.AbstractProxyInvoker.invoke(AbstractProxyInvoker.java:76)
    at com.alibaba.dubbo.config.invoker.DelegateProviderMetaDataInvoker.invoke(DelegateProviderMetaDataInvoker.java:52)
    at com.alibaba.dubbo.rpc.protocol.InvokerWrapper.invoke(InvokerWrapper.java:56)
    at com.alibaba.dubbo.rpc.filter.ExceptionFilter.invoke(ExceptionFilter.java:62)
    at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:72)
    at com.alibaba.dubbo.monitor.support.MonitorFilter.invoke(MonitorFilter.java:75)
    at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:72)
    at com.alibaba.dubbo.rpc.filter.TimeoutFilter.invoke(TimeoutFilter.java:42)
    at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:72)
    at com.alibaba.dubbo.rpc.protocol.dubbo.filter.TraceFilter.invoke(TraceFilter.java:78)
    at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:72)
    at com.alibaba.dubbo.rpc.filter.ContextFilter.invoke(ContextFilter.java:72)
    at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:72)
    at com.alibaba.dubbo.rpc.filter.GenericFilter.invoke(GenericFilter.java:131)
    at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:72)
    at com.alibaba.dubbo.rpc.filter.ClassLoaderFilter.invoke(ClassLoaderFilter.java:38)
    at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:72)
    at com.alibaba.dubbo.rpc.filter.EchoFilter.invoke(EchoFilter.java:38)
    at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:72)
    at com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol$1.reply(DubboProtocol.java:103)
    at com.alibaba.dubbo.remoting.exchange.support.header.HeaderExchangeHandler.handleRequest(HeaderExchangeHandler.java:96)
    at com.alibaba.dubbo.remoting.exchange.support.header.HeaderExchangeHandler.received(HeaderExchangeHandler.java:172)
    at com.alibaba.dubbo.remoting.transport.DecodeHandler.received(DecodeHandler.java:51)
    at com.alibaba.dubbo.remoting.transport.dispatcher.ChannelEventRunnable.run(ChannelEventRunnable.java:80)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:750)

NoClassDefFoundError

这个日志让人摸不到头脑,jdk自己的类也会加载不到?这个肯定不是最终原因。接着往前找异常日志,结果发现。有堆栈溢出。

java.lang.StackOverflowError: null
    at java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater(AtomicReferenceFieldUpdater.java:110)
    at java.sql.SQLException.<clinit>(SQLException.java:372)
    at com.alibaba.druid.pool.DruidDataSource.handleConnectionException(DruidDataSource.java:1543)
    at com.alibaba.druid.pool.DruidPooledConnection.handleException(DruidPooledConnection.java:133)
    at com.alibaba.druid.pool.DruidPooledStatement.checkException(DruidPooledStatement.java:77)
    at com.alibaba.druid.pool.DruidPooledPreparedStatement.execute(DruidPooledPreparedStatement.java:502)
    at org.apache.ibatis.executor.statement.PreparedStatementHandler.query(PreparedStatementHandler.java:63)
    at org.apache.ibatis.executor.statement.RoutingStatementHandler.query(RoutingStatementHandler.java:79)
    at org.apache.ibatis.executor.SimpleExecutor.doQuery(SimpleExecutor.java:63)
    at org.apache.ibatis.executor.BaseExecutor.queryFromDatabase(BaseExecutor.java:324)
    at org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:156)
    at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:109)
    at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:83)
    at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:148)
    at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:141)
    at sun.reflect.GeneratedMethodAccessor276.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:434)
    at com.sun.proxy.$Proxy150.selectList(Unknown Source)
    at org.mybatis.spring.SqlSessionTemplate.selectList(SqlSessionTemplate.java:231)
    at org.apache.ibatis.binding.MapperMethod.executeForMany(MapperMethod.java:137)
    at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:75)
    at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:53)
    at com.sun.proxy.$Proxy169.getCardInfo(Unknown Source)
    at com.custom.hsi.mm.masterdb.service.impl.IMasterServiceImpl.getCardInfo(IMasterServiceImpl.java:1658)
    at com.custom.hsi.mm.masterdb.service.impl.IMasterServiceImpl.getCardInfo(IMasterServiceImpl.java:1668)
    at com.custom.hsi.mm.masterdb.service.impl.IMasterServiceImpl.getCardInfo(IMasterServiceImpl.java:1668)
    at com.custom.hsi.mm.masterdb.service.impl.IMasterServiceImpl.getCardInfo(IMasterServiceImpl.java:1668)
    at com.custom.hsi.mm.masterdb.service.impl.IMasterServiceImpl.getCardInfo(IMasterServiceImpl.java:1668)
    at com.custom.hsi.mm.masterdb.service.impl.IMasterServiceImpl.getCardInfo(IMasterServiceImpl.java:1668)
...后续都是这个类

分析后,发现出问题的位置是,SQLException.java 中的静态属性初始化报错

​编辑

但是这里报错为啥会影响后面所有的正常请求呢。

核心根因

因为 JVM 的类加载器有“懒加载”机制,只加载一次,失败后也不会重试。

即使你后来修复了递归调用,但如果你不重启 JVM,该类依然处于“初始化失败”状态。

某个请求触发了 SQLException 的构造

构造时由于线程栈已满,导致初始化失败

后续所有请求,哪怕没有异常,只要用了 SQLException,就会失败

从这一刻起:

使用方式是否受影响
new SQLException(...)❌ 报错:NoClassDefFoundError
catch (SQLException e)❌ 报错:NoClassDefFoundError
throws SQLException❌ 方法签名中声明也受影响
Class.forName("java.sql.SQLException")❌ 同样报错

也就是说,只要你在代码中使用了 SQLException 这个类名,不管是不是真的抛出异常,都会失败。

示例说明

来看一段完整示例:

import com.alibaba.fastjson.JSONObject;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.sql.SQLException;


public class SQLExceptionSimulator {

    public static void main(String[] args) {

       try {
           new Test();
       } catch (Exception e) {
           e.printStackTrace();
       } catch (Throwable e) {
           e.printStackTrace();
       }

        try {
            Test2.x = false;
            new Test();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static class Test2{
        private static Boolean x = true;
        static Boolean get() {
            return x;
        }
    }

    private static class Test{
        Test() {
        }
        static {
            if (Test2.get()) {
                throw new RuntimeException("");
            }
        }
    }

}

如果第一次 new Test() 时因为异常失败了:

那么第二次 new Test(); 也会失败:

​编辑