谁还没遇上过NoClassDefFoundError咋地——浅谈字节码生成与热部署

谁还没遇上过NoClassDefFoundError咋地——浅谈字节码生成与热部署 前言

在Java程序员的世界里,NoClassDefFoundError是一类相当令人厌恶的错误,因为这类错误通常非常隐蔽,难以调试。
通常,NoClassDefFoundError被认为是运行时类加载器无法在classpath下找不到需要的类,而该类在编译时是存在的,这就通常预示着一些很麻烦的情况,例如:

不同版本的包冲突。这是最最最常见的情况,尤其常见于用户代码需要运行于容器中,而本地容器和线上容器版本不同时;

使用了多个classloader。要用的类被另一个类加载器加载了,导致当前类加载器作用域内找不到这个类,在破坏双亲委托时容易出这样的问题;

除了上面提到的这几种问题,还有一些可能导致这个错误的特殊案例,比如今天我遇到的这个:

问题背景

一个spring boot程序,maven打包本地运行毫无问题,发布到生产环境就会biang的报一个错说NoClassDefFoundError。

该问题的隐蔽之处在于没有办法在本地复现,所以觉得有必要跟大家分享。

分析过程

第一反应,maven环境问题。我本地的maven连的是central仓库,而线上环境连得是公司的私有仓库。我司的maven仓库被各种开发人员胡乱上传的包弄的很像薛定谔的猫,鬼才知道它给你的哪个包是不是你想要的。
如果它提供的包事实上是错误的,或者经过第三方(其他开发)的修改,那很容易造成这个错误。

排查这个其实也好办,两种方式一是打thin jar然后自己上传依赖,二是找运维做一套独立的maven环境,使用和本地相同的配置,总之一通折腾之后,重新部署,发现错误还在。

不是包版本错误的话,就比较隐蔽了。因为该程序在本地运行可以通过所有测试用例,也没有在不同的线程里狂秀classloader骚操作,所以也基本排除上面提到的2和3的可能性。

都不是的情况下,返回头去重新看了一下错误日志,发现虽然报的是NoClassDefFoundError,但后面跟的消息是类实例化失败,这个消息给了我关键的提醒。

NoClassDefFoundError是一个非常晦涩的错误,有一些意外的情况我认为其实不适合归到这个错误里,比如这次的类实例化错误,或者确切的说,类初始化错误

回到本文来,这个错误日志里写了什么呢?日志告诉我,我的一个类cinit失败,错误在第多少多少行。只有这一个错误堆栈,没有输出任何其他的错误信息,比如到底什么原因导致这个类cinit失败了。出错的代码在org.apache.logging.log4j.status.StatusLogger这个类中,代码如下所示:

private static final PropertiesUtil PROPS = new PropertiesUtil("log4j2.StatusLogger.properties");

这里就是另外一种会导致NoClassDefFoundError发生的场合:在静态字段和静态代码块初始化时的异常导致类初始化失败,会产生NoClassDefFoundError。
光看这句话是看不出什么可能出错的地方来的,我们跟进去看看里面的代码有哪个地方有问题:

//PropertyUtil.java private static final String LOG4J_PROPERTIES_FILE_NAME = "log4j2.component.properties"; private static final PropertiesUtil LOG4J_PROPERTIES = new PropertiesUtil(LOG4J_PROPERTIES_FILE_NAME); public PropertiesUtil(final String propertiesFileName) { this.environment = new Environment(new PropertyFilePropertySource(propertiesFileName)); } //Enviroment.java private final Set<PropertySource> sources = new TreeSet<>(new PropertySource.Comparator()); private final Map<CharSequence, String> literal = new ConcurrentHashMap<>(); private final Map<CharSequence, String> normalized = new ConcurrentHashMap<>(); private final Map<List<CharSequence>, String> tokenized = new ConcurrentHashMap<>(); private Environment(final PropertySource propertySource) { sources.add(propertySource); for (final PropertySource source : ServiceLoader.load(PropertySource.class)) { sources.add(source); } reload(); } private synchronized void reload() { literal.clear(); normalized.clear(); tokenized.clear(); for (final PropertySource source : sources) { source.forEach(new BiConsumer<String, String>() { @Override public void accept(final String key, final String value) { literal.put(key, value); final List<CharSequence> tokens = PropertySource.Util.tokenize(key); if (tokens.isEmpty()) { normalized.put(source.getNormalForm(Collections.singleton(key)), value); } else { normalized.put(source.getNormalForm(tokens), value); tokenized.put(tokens, value); } } }); } } //PropertyFilePropertySource.java public PropertyFilePropertySource(final String fileName) { super(loadPropertiesFile(fileName)); } private static Properties loadPropertiesFile(final String fileName) { final Properties props = new Properties(); for (final URL url : LoaderUtil.findResources(fileName)) { try (final InputStream in = url.openStream()) { props.load(in); } catch (IOException e) { LowLevelLogUtil.logException("Unable to read " + url, e); } } return props; } //PropertiesPropertySource.java PropertyFilePropertySource类的父类 public PropertiesPropertySource(final Properties properties) { this.properties = properties; } @Override public void forEach(final BiConsumer<String, String> action) { for (final Map.Entry<Object, Object> entry : properties.entrySet()) { action.accept(((String) entry.getKey()), ((String) entry.getValue())); } }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpxypp.html