Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

class redefinition failed: attempted to change the schema (add/remove fields) 分析排查 #2165

Open
hengyunabc opened this issue Apr 26, 2022 · 3 comments

Comments

@hengyunabc
Copy link
Collaborator

hengyunabc commented Apr 26, 2022

结论

出现这个异常的原因是很明确的,在更新字节码时,增加/删除了field。可能有下面的原因:

  1. JDK的bug,尽量用最新的JDK版本,特别是JDK8

    比如: redefine热更新报错 #969 (comment)

  2. 其它的java agent修改了字节码,增加了 field。

    比如jacoco,这个代码测试覆盖率工具,它会在类里增加一个static field来记录执行信息。

  3. 其它的java agent修改了字节码,但是在retransform时,生成同样的字节码

    比如skywalking: 无法trace 和watch加载skywalking7 的jar中的类方法 #1141

  4. 在执行arthas redefine/retransform命令时,用户自己想替换的字节码有修改

    • 可能是用户本地的JDK版本和线上的JDK版本不一致导致的
    • 可能是用户自己修改了源码

理解ClassFileTransformer机制

从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;

简而言之:

  1. transform函数传入字节码byte[],再返回新的字节码byte[]
  2. 因为JVM里注册的ClassFileTransformer可能有多个,那么在JVM里运行的字节码里,可能是被多个ClassFileTransformer处理过的。
  3. 触发了retransformClasses之后,多个agent注册的多个ClassFileTransformer会被依次回调,上一个处理的字节码传递到下一个。
    所以不能保证这些ClassFileTransformer第二次执行会返回同样的结果。

所以,重点结论来了:

  1. 每个agent注册的ClassFileTransformer要支持多次执行,每次生成同样的结果。
  2. 可以在ClassFileTransformer增加field不?可以的,但要保证第一条。
  3. 如果不能保证ClassFileTransformer每次生成同样的结果,就很容易出现class redefinition failed: attempted to change the schema (add/remove fields)
  4. 有的agent实现不合理,注册ClassFileTransformer之后,做完transform之后(可能修改了schema,add/remove fields),它把注册的ClassFileTransformer删掉了。那么当别的agent再次触发retransform时,就会出错了。

排查

  1. 检查java应用有多少个java agent,可能是配置在启动参数上,也有可能是动态attach上去的
  2. 检查这些agent做了transform之后,生成的字节码是怎样的
  3. arthas自身有一个options dump true的开关,打开之后,arthas会把经过自己ClassFileTransformer处理过的字节码保存到arthas-class-dump目录,方便分析排查
  4. 利用工具把字节码dump之后分析,比如使用arthas的dump命令,或者这个工具: https://github.com/hengyunabc/dumpclass
  5. 在issue里多搜下
@sohouse
Copy link

sohouse commented Apr 28, 2022

我现在遇到个问题,
docker里安装1.8.0_212版本的JDK,arthas3.5.5版本
exec到docker里进入arthas,执行watch命令
输出class redefinition failed: attempted to change the schema (add/remove fields) 异常。。
watch和trace都会,按说这两个命令不会更改到字节码。jad对应的方法后也不是反编译的源码,方法体不见了,输出了return (List)delegate$kilt9k0.intercept(this, new Object[0], (Callable<?>)new auxiliary.kY2cigI3(this), cachedValue$0JsIgyaN$ufneft0);
有人遇到过没有

@hengyunabc
Copy link
Collaborator Author

@sohouse 试下最新版本arthas,jdk8也试下最新的。

@stddu
Copy link

stddu commented Apr 17, 2024

@hengyunabc 您好,项目中用了jacoco 的javaagent,有什么办法继续用redefine/retransform命令吗?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants