出现这个异常的原因是很明确的,在更新字节码时,增加/删除了field。可能有下面的原因:
-
JDK的bug,尽量用最新的JDK版本,特别是JDK8
比如: https://github.com/alibaba/arthas/issues/969#issuecomment-564394725
-
其它的java agent修改了字节码,增加了 field。
比如
jacoco,这个代码测试覆盖率工具,它会在类里增加一个static field来记录执行信息。 -
其它的java agent修改了字节码,但是在
retransform时,生成同样的字节码比如skywalking: https://github.com/alibaba/arthas/issues/1141
-
在执行arthas
redefine/retransform命令时,用户自己想替换的字节码有修改- 可能是用户本地的JDK版本和线上的JDK版本不一致导致的
- 可能是用户自己修改了源码
- 最好的参考文档是JDK自身的: https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/ClassFileTransformer.html
从JDK 1.5起,有一套ClassFileTransformer的机制,Java Agent通过Instrumentation注册ClassFileTransformer,那么在类加载或者retransform时就可以回调修改字节码。
显然,在Arthas里,要增强的类是已经被加载的,所以它们的字节码都是在retransform时被修改的。
通过显式调用Instrumentation.retransformClasses(Class<?>...)可以触发回调。
Arthas里字节码相关的命令watch/trace/stack/tt/dump/jad/retransform等命令都是通过ClassFileTransformer来实现的。
ClassFileTransformer的接口如下:
public interface ClassFileTransformer {
byte[]
transform( ClassLoader loader,
String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer)
throws IllegalClassFormatException;
简而言之:
transform函数传入字节码byte[],再返回新的字节码byte[]- 因为JVM里注册的
ClassFileTransformer可能有多个,那么在JVM里运行的字节码里,可能是被多个ClassFileTransformer处理过的。 - 触发了
retransformClasses之后,多个agent注册的多个ClassFileTransformer会被依次回调,上一个处理的字节码传递到下一个。 所以不能保证这些ClassFileTransformer第二次执行会返回同样的结果。
所以,重点结论来了:
- 每个agent注册的
ClassFileTransformer要支持多次执行,每次生成同样的结果。 - 可以在
ClassFileTransformer增加field不?可以的,但要保证第一条。 - 如果不能保证
ClassFileTransformer每次生成同样的结果,就很容易出现class redefinition failed: attempted to change the schema (add/remove fields)。 - 有的agent实现不合理,注册
ClassFileTransformer之后,做完transform之后(可能修改了schema,add/remove fields),它把注册的ClassFileTransformer删掉了。那么当别的agent再次触发retransform时,就会出错了。
- 检查java应用有多少个java agent,可能是配置在启动参数上,也有可能是动态attach上去的
- 检查这些agent做了
transform之后,生成的字节码是怎样的 - arthas自身有一个
options dump true的开关,打开之后,arthas会把经过自己ClassFileTransformer处理过的字节码保存到arthas-class-dump目录,方便分析排查 - 利用工具把字节码dump之后分析,比如使用arthas的dump命令,或者这个工具: https://github.com/hengyunabc/dumpclass
- 在issue里多搜下