2.14.3版本在JDK 21中遇到了类转换异常问题,TTL是否支持JDK 21?
brucelwl opened this issue · 13 comments
2.14.3版本是不是还不支持JDK21??
@wuwen5 异常信息如下, 这个问题在Idea中启动ttl 的 agent能够复现, 以命令方式挂载agent启动没有问题, 猜测是和idea的debug agent有冲突
2023-09-28 10:53:34.927 SEVERE [main] TtlTransformer: Fail to transform class sun/net/httpserver/ServerImpl$ReqRspTimeoutTask, cause: java.lang.NullPointerException: Cannot read field "string" because "utf" is null
java.lang.NullPointerException: Cannot read field "string" because "utf" is null
at com.alibaba.ttl.threadpool.agent.internal.javassist.bytecode.ConstPool.getUtf8Info(ConstPool.java:675)
at com.alibaba.ttl.threadpool.agent.internal.javassist.bytecode.MethodParametersAttribute.copy(MethodParametersAttribute.java:90)
at com.alibaba.ttl.threadpool.agent.internal.javassist.bytecode.AttributeInfo.copyAll(AttributeInfo.java:249)
at com.alibaba.ttl.threadpool.agent.internal.javassist.bytecode.MethodInfo.compact(MethodInfo.java:157)
at com.alibaba.ttl.threadpool.agent.internal.javassist.bytecode.ClassFile.compact(ClassFile.java:242)
at com.alibaba.ttl.threadpool.agent.internal.javassist.CtClassType.toBytecode(CtClassType.java:1583)
at com.alibaba.ttl.threadpool.agent.internal.javassist.CtClass.toBytecode(CtClass.java:1519)
at com.alibaba.ttl.threadpool.agent.TtlTransformer.transform(TtlTransformer.java:81)
at java.instrument/java.lang.instrument.ClassFileTransformer.transform(ClassFileTransformer.java:244)
at java.instrument/sun.instrument.TransformerManager.transform(TransformerManager.java:188)
at java.instrument/sun.instrument.InstrumentationImpl.transform(InstrumentationImpl.java:610)
at java.base/java.lang.ClassLoader.defineClass2(Native Method)
at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1118)
at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:182)
at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:821)
at java.base/jdk.internal.loader.BuiltinClassLoader.findClassInModuleOrNull(BuiltinClassLoader.java:741)
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:665)
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:639)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:526)
at jdk.httpserver/sun.net.httpserver.HttpServerImpl.<init>(HttpServerImpl.java:50)
at jdk.httpserver/sun.net.httpserver.DefaultHttpServerProvider.createHttpServer(DefaultHttpServerProvider.java:35)
at jdk.httpserver/com.sun.net.httpserver.HttpServer.create(HttpServer.java:152)
at jdk.httpserver/com.sun.net.httpserver.HttpServer.create(HttpServer.java:126)
at io.prometheus.client.exporter.HTTPServer.<init>(HTTPServer.java:144)
at io.prometheus.client.exporter.HTTPServer.<init>(HTTPServer.java:165)
应该是javassist
在处理 sun/net/httpserver/ServerImpl$ReqRspTimeoutTask
字节码的时候出现了问题。
已提交到javassist
:
知道怎么回事了,问题出现在内部类的字节码发生了细微变化,在java8之后,当使用 -parameters
选项编译Java代码时,编译器会将方法参数的名称存储在类文件的调试信息中。这样,当你使用调试器来调试程序时,可以看到方法的参数名称而不仅仅是它们的类型。
使用-parameters
编译时,class信息如下 (当未使用-parameters编译时,不会携带MethodParameters
)
public void test(int, java.lang.String);
descriptor: (ILjava/lang/String;)V
flags: (0x0001) ACC_PUBLIC
Code:
stack=0, locals=3, args_size=3
0: return
LineNumberTable:
line 4: 0
MethodParameters:
Name Flags
i
s
默认在jdk中的class应该是没有携带-parameters
编译的,所以默认不会携带MethodParameters
,目前查看class也是这样。
但是在Java21中编译内部类时,即使没携带-parameters
编译时,构造函数仍然会携带MethodParameters
,这与其他版本jdk字节码不一致,这也是不合理和非预期的地方。
当使用-parameters
编译含内部类时,jdk8、jdk17、jdk21对MethodParameters
处理一致,如下(有一个参数,参数名称为this$0
)
test4.MethodParamInnerClassTest$InnerClass(test4.MethodParamInnerClassTest);
descriptor: (Ltest4/MethodParamInnerClassTest;)V
flags: (0x0000)
Code:
(略)
MethodParameters:
Name Flags
this$0 final mandated
当未使用-parameters
编译含内部类时,jdk8\jdk17如下,没有MethodParameters
test4.MethodParamInnerClassTest$InnerClass(test4.MethodParamInnerClassTest);
descriptor: (Ltest4/MethodParamInnerClassTest;)V
flags: (0x0000)
Code:
(略)
而java21如下,仍然携带了MethodParameters
,表示有一个参数,但无法获取参数名称 (<no name>
)。
test4.MethodParamInnerClassTest$InnerClass(test4.MethodParamInnerClassTest);
descriptor: (Ltest4/MethodParamInnerClassTest;)V
flags: (0x0000)
Code:
(略)
MethodParameters:
Name Flags
<no name> final mandated
所以,总结为,java21编译的内部类字节码发生了变化,引起的兼容性问题。
javassist
在解析java21编译的内部类字节码时,在对MethodParameters
字节码的解析时无法从常量池中获取字符信息。
大致可以这么理解,字节码告诉了构造方法有一个参数,参数名对应常量池表的下标为0,常量池表信息从下标1开始,0为jvm保留(具体不详 // index 0 is reserved by the JVM.
),即无法从常量池获取字符串信息。
JVM规范:
@wuwen5 是不是意味着即使用jdk8 在添加
-parameters
参数的情况下,也会触发这个异常?
不会
添加-parameters
参数时,无论是java21还是java8都会正确符合预期的带上MethodParameters
信息,此时javassist
能正常解析。
现在问题是没使用-parameters
参数时,预期是所有方法都不会带MethodParameters
,但是java21却在内部类的构造方法中带了没有名称的MethodParameters
。
先记录下可能与之相关的信息
- openjdk/jdk#13167
- openjdk/jdk#9862
- Classfile API throws IOOBE for MethodParameters attribute without parameter names https://bugs.openjdk.org/browse/JDK-8304837
进一步研究发现,影响范围为java21编译出来的非静态的内部类
非静态内部类, 包含MethodParameters
<no name>
Java21InnerClassWithoutParameters$InnerClass(Java21InnerClassWithoutParameters);
descriptor: (LJava21InnerClassWithoutParameters;)V
flags: (0x0000)
Code:
stack=1, locals=2, args_size=2
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 2: 0
MethodParameters:
Name Flags
<no name> final mandated
静态内部类,不含MethodParameters
Java21InnerClassWithoutParameters$StaticInnerClass();
descriptor: ()V
flags: (0x0000)
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 7: 0
虚拟机规范允许MethodParameters里面项目名称指向0,表示参数没有名称。javaassist没有考虑这种情况,因为之前好像没有编译器在里面放0所以实际使用时javaassist没出过错。你可以编译个带Optional参数的enum:
enum TestEnum {
INSTANCE(Optional.of("A"));
TestEnum(Optional<String> opt) {}
}
也会受影响,因为编译器加了name ordinal两个参数,类似inner class有个outer this参数。
这个改动主要是为了修这个例子里面的一个bug:本来constructor.getParameters()[2].getParameterizedType()
返回是Optional.class
,这个改动后因为编译器加信息,会返回代表Optional<String>
的Type
当前已发布的javassist-3.30.1-GA
已经修复了这个问题,但是由于合入了其他修改,编译的字节码升级到了Java 11
,因此3.30.1-GA
这个版本我们暂时还不能直接升级,需等待下个版本发布.
当前已发布的
javassist-3.30.1-GA
已经修复了这个问题,但是由于合入了其他修改,编译的字节码升级到了JDK-11
我也发现了javassist-3.30.1-GA
升级到Java 11
😰
看到你在推进让javassist
新版继续支持Java 8
了 🚀👍