记录一次因JVM加载类顺序引起的 NoSuchMethodException

事故起因

项目上有一个需求,需要通过API的方式,滚动重启部署在k8s上的服务。

解决方案也挺简单的,通过调用k8s 的API service 就能解决,也有现成的客户端框架kubernetes-client/java。

整个实现机制可以参考k8s 的API Service介绍

然而整套逻辑写下来本地测试完全没有问题,一部署到k8s pod里问题就来了,报错 NoSuchMethodException。

从错误日志可以看出是一个 com.google.gson.stream.JsonWriter 没有 jsonValue(String)这个方法。

定位问题


第一反应,gson包冲突了。

赶紧打印了依赖树

./gradlew :app:dependencies

确实有一个gson的依赖,一看版本,再去看对应的类,诶?!这个方法,是有的啊!

这就很诡异了。

之后尝试了各种方案:

排除 exclude 有可能依赖的低版本gson,不行

强制指定依赖高版本gson,不行

这里已经有点崩溃了,本地完全没有问题,一到服务器上问题就来了!此时半天已经没有了。


迫于开发压力,我们尝试把gson的源码复制到项目源码里,这样就算有依赖冲突,按照优先顺序,总应该先调用项目里面的类吧!

说干就干,我们复制了一份JsonWriter到源码里,保持了相同的包名,然后本地测试没有问题之后,再一次打包编译生成镜像,部署到k8s上。

然鹅,还是一样的问题,NoSuchMethodException。

这是什么情况?不应该啊?!!!

此时我非常好奇这个服务器上用的JsonWriter是哪儿来的了。因为一天已经接近尾声了。

k8s 定位问题

这里用到了阿里的arthas工具。

先下载工具

curl -O http://arthas.aliyun.com/arthas-boot.jar

然后因为jdk版本的原因,必须把jdk整套复制到container里

kubectl cp arthas-boot.jar {pod-name}:/home -c {containerName}
kubectl cp java11 {pod-name}:/home -c {containerName}

然后进入container中

kubectl exec -it {pod} -c {containerName} -- /bin/bash

查看java使用的pid

ps aux|grep java

这里可以看到pid是 13

启动arthas

java11/bin/java -jar arthas-boot.jar 13

先找到AppClassLoader的hashcode,比如你查看一个自己的静态类,或者主类都行,后面就有类加载器的hashcode

sc -d com.test.Main

然后用这个类加载器加载目标类JsonWriter

classloader -c 3d4eac69 --load com.google.gson.stream.JsonWriter

之后就有类的信息了,也可以用上面的类扫描命令查看 sc

不看不知道,一看吓一跳

code-source 居然路径是 /opt/lib/gson.2.2.4.jar

这个是什么概念?

要知道我们项目gradle编译后的jar包只应该有2个,要么是app-SNAPSHOT.jar 要么是 app-dependencies.jar。前者是项目的源码,后者是所有的依赖。

结果这个JsonWriter居然是单独出现的,这就只能说明一点,这个jar包,是在container里的!

最后发现是项目docker file引用的基础镜像是公司其他项目组做的,他们在/opt下手动放了这个jar包。

那为什么手动放个jar包就会出问题呢?

原因分析

如果是项目内的依赖问题,是可以通过配置解决依赖冲突或者类加载顺序的。

但是这个是和项目代码并列的jar包,都是放在classpath下的。

就是说A.jar包里有一个JsonWriter,B.jar包里也有一个JsonWriter。那么加载jar包的顺序是自然顺序。

然后根据JVM类加载机制的逻辑,对已经加载过一次的类,不会再加载第二次。所以导致类永远都是低版本的JsonWriter在使用!

也就是说本地永远是对的,一上服务器,就会出现这样的错误。

解决方案

在docker file里手动删掉这个jar就一切恢复如初啦!

 

1人评论了“记录一次因JVM加载类顺序引起的 NoSuchMethodException”

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注

Scroll to Top