『StabilityGuide』是阿里多位阿里技术工程师共同发起的稳定性领域的知识库开源项目,涵盖性能压测、故障演练、JVM、应用容器、服务框架、流量调度、监控、诊断等多个技术领域,以更结构化的方式来打造稳定性领域的知识库,欢迎您的加入。java
在实际生产系统中,咱们主要关注运行时抛出的 NoSuchMethodError 错误,该错误轻则致使程序异常终止,严重时甚至会产生不可预知的程序结果,好比支付服务执行异常,实际支付已完成,却向用户返回支付失败。app
运行时抛出 NoSuchMethodError 错误的根本缘由就是: 应用程序直接或间接依赖了同一个类的多个版本,而且在运行时执行了缺乏方法的版本。 以下图所示:框架
所以,核心问题就转化为: 同一类为何会有多个版本?哪一个版本的类最终会被执行?ide
致使 Java Class 出现多版本的缘由,能够概括为如下几类:工具
影响 Class 最终是否被执行的关键因素有两个:Maven 依赖仲裁机制和 JVM 类加载机制,以下图所示:性能
首先,Maven 依赖仲裁机制 决定了打包的优先级, 仲裁优先级“从高到低”以下所述:测试
合理使用 Maven 依赖仲裁机制能够便捷的管理 Jar 包版本,而不合理的使用将致使多版本 Jar 冲突。ui
其次,JVM 类加载机制 决定了 Class 被加载到 JVM 的优先级, 若是同一个类出如今多个 Jar 包中,那么在双亲委派类加载机制下,加载该 Jar 包的类加载器层级越高,该 Jar 包越先被加载,它所包含的 Class 越先被执行,如上图所示:spa
除了上述两种缘由外,在同一个 ClassLoader 下,若是存在一个 Class 出如今不一样的 Jar 包中,那么文件系统的文件加载顺序也可能会影响最终的加载结果。所以,应该尽可能保证开发/测试/生产系统环境一致性。日志
虽然抛出 NoSuchMethodError 错误的缘由多种多样,但本质上是因为编译时类路径与运行时类路径不一致。所以,通用的定位思路能够概括为如下 3 步:
一、定位异常 Class 的全限定类名与调用方,一般能够在应用日志抛出的异常堆栈中获取。以下图所示:
Exception in thread "main" java.lang.NoSuchMethodError: com.xxx.AsyncAppender.append(Ljava/lang/String;)Ljava/lang/String; at com.xxx.ProvokeNoSuchMethodError.main(ProvokeNoSuchMethodError:7) at ……
二、定位异常 Class 的来源,能够经过 Arthas 等在线诊断工具反编译,如 jad com.xxx.AsyncAppender,获取该类运行时的源码、ClassLoader、Jar 包位置等信息。
若是应用程序启动失败,或者没法进行在线诊断,能够考虑添加 JVM 启动参数 -verbose:class 或 -XX:+TraceClassLoading,在日志中将输出每一个类的加载信息,好比来自哪一个 Jar 包。
三、根据 ClassLoader 和 Jar 包全路径名等信息,判断是类加载、Maven 仲裁或其余缘由,并对应的加以解决。
若是是同一个 Jar 包的多版本问题,能够在 Maven 标签中指定实际须要的版本,或者移除间接依赖中的低版本(提示: 执行 mvn dependency:tree 命令,能够查看 Maven 依赖拓扑关系)。
若是是同一个 Class 出如今不一样的 Jar 包问题,若能够排除,就用 排除该依赖;如不能排除,则考虑升级或替换为其余 Jar 包,或者考虑使用 ClassLoader 隔离技术,可参考 《若是jar包冲突不可避免,如何实现jar包隔离?》。
本文介绍的 Jar 包冲突解决方法,除了解决 java.lang.NoSuchMethodError 之外,对其余类似问题也具有必定的参考价值。
例如 java.lang.ClassNotFoundException,即加载不到指定类,一般是 Maven 仲裁选错了版本,如本地开发阶段调用了 1.2.0 版本,而打包时采用了 1.0.0 版本的 Jar 包。同理,java.lang.NoClassDefFoundError 和 java.lang.LinkageError 也能够基于上述思路进行排查。
此外,若是类和方法名都保持不变,可是内部实现有变化,在多版本冲突场景下,不会抛出异常,但程序行为跟预期不一致, 此时,也能够基于上述思路进行排查诊断。
原文连接 本文为云栖社区原创内容,未经容许不得转载。