-
Notifications
You must be signed in to change notification settings - Fork 117
Open
Description
Description
我们发现同一个测试用例在不同的OpenJDK Verison上的结果不同,具体而言JDK8正常运行,JDK11、17、21抛出IncompatibleClassChangeError。
Steps to Reproduce
复现:
>>> path_to_jdk8/bin/java -cp . TestLauncher
>>> path_to_jdk11/bin/java -cp . TestLauncher
>>> path_to_jdk17/bin/java -cp . TestLauncher
>>> path_to_jdk21/bin/java -cp . TestLauncher
实际结果:
jdk8:
TestClass1 correctly overrides conflicting methods.
TestClass2 does not override conflicting methods.
InvalidTestClass should throw an error for conflicting methods.
jdk11:
Exception in thread "main" java.lang.IncompatibleClassChangeError: Method 'void TestClass1.run()' must be InterfaceMethodref constant
at TestLauncher.main(Unknown Source)
jdk17:
Exception in thread "main" java.lang.IncompatibleClassChangeError: Method 'void TestClass1.run()' must be InterfaceMethodref constant
at TestLauncher.main(Unknown Source)
jdk21:
Exception in thread "main" java.lang.IncompatibleClassChangeError: Method 'void TestClass1.run()' must be InterfaceMethodref constant
at TestLauncher.main(Unknown Source)
Expected behavior
根据上述的执行结果,我们首先使用javap将TestLauncher反汇编,得到结果:
Classfile /xxx/TestLauncher.class
Last modified 2025年7月24日; size 536 bytes
MD5 checksum e4b325e8eeac8e5df91e606cc434ddc6
public class TestLauncher
minor version: 0
major version: 52
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #2 // TestLauncher
super_class: #4 // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 0
Constant pool:
#1 = Utf8 TestLauncher
#2 = Class #1 // TestLauncher
#3 = Utf8 java/lang/Object
#4 = Class #3 // java/lang/Object
#5 = Utf8 main
#6 = Utf8 ([Ljava/lang/String;)V
#7 = Utf8 TestClass1
#8 = Class #7 // TestClass1
#9 = Utf8 run
#10 = Utf8 ()V
#11 = NameAndType #9:#10 // run:()V
#12 = Methodref #8.#11 // TestClass1.run:()V
#13 = Utf8 TestClass2
#14 = Class #13 // TestClass2
#15 = Methodref #14.#11 // TestClass2.run:()V
#16 = Utf8 InvalidTestClass
#17 = Class #16 // InvalidTestClass
#18 = Methodref #17.#11 // InvalidTestClass.run:()V
#19 = Utf8 java/lang/System
#20 = Class #19 // java/lang/System
#21 = Utf8 out
#22 = Utf8 Ljava/io/PrintStream;
#23 = NameAndType #21:#22 // out:Ljava/io/PrintStream;
#24 = Fieldref #20.#23 // java/lang/System.out:Ljava/io/PrintStream;
#25 = Utf8 InvalidTestClass load fail:
#26 = String #25 // InvalidTestClass load fail:
#27 = Utf8 java/io/PrintStream
#28 = Class #27 // java/io/PrintStream
#29 = Utf8 print
#30 = Utf8 (Ljava/lang/String;)V
#31 = NameAndType #29:#30 // print:(Ljava/lang/String;)V
#32 = Methodref #28.#31 // java/io/PrintStream.print:(Ljava/lang/String;)V
#33 = Utf8 java/lang/Throwable
#34 = Class #33 // java/lang/Throwable
#35 = Utf8 toString
#36 = Utf8 ()Ljava/lang/String;
#37 = NameAndType #35:#36 // toString:()Ljava/lang/String;
#38 = Methodref #34.#37 // java/lang/Throwable.toString:()Ljava/lang/String;
#39 = Utf8 println
#40 = NameAndType #39:#30 // println:(Ljava/lang/String;)V
#41 = Methodref #28.#40 // java/io/PrintStream.println:(Ljava/lang/String;)V
#42 = Utf8 Code
#43 = Utf8 StackMapTable
{
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: invokestatic #12 // Method TestClass1.run:()V
3: invokestatic #15 // Method TestClass2.run:()V
6: invokestatic #18 // Method InvalidTestClass.run:()V
9: goto 31
12: astore_1
13: getstatic #24 // Field java/lang/System.out:Ljava/io/PrintStream;
16: ldc #26 // String InvalidTestClass load fail:
18: invokevirtual #32 // Method java/io/PrintStream.print:(Ljava/lang/String;)V
21: getstatic #24 // Field java/lang/System.out:Ljava/io/PrintStream;
24: aload_1
25: invokevirtual #38 // Method java/lang/Throwable.toString:()Ljava/lang/String;
28: invokevirtual #41 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
31: return
Exception table:
from to target type
6 9 12 Class java/lang/Throwable
StackMapTable: number_of_entries = 2
frame_type = 76 /* same_locals_1_stack_item */
stack = [ class java/lang/Throwable ]
frame_type = 18 /* same */
}
我们观察到,在 TestLauncher.main 中,指令 TestClass1.run(); 使用的是 invokestatic,而解析的目标 TestClass1 是一个接口。根据 JVM SE8 §6.5 invokestatic 的规定:“指定的方法将被解析(§5.4.3.3)。”而 第 5.4.3.3 章 明确指出:“1. 如果 C 是一个接口,方法解析将抛出 IncompatibleClassChangeError。”因此,在 jdk8 中,应抛出 IncompatibleClassChangeError,而不是正常执行该方法。
同样地,根据 JVM SE11 §6.5 invokestatic 的规定:“指定的方法将被解析(§5.4.3.3、§5.4.3.4),”该版本允许在接口上解析方法,因此在 jdk11 中,测试用例应正常执行,而不是抛出 IncompatibleClassChangeError。同理V17,V21也应正常运行。
JDK version
openjdk version "1.8.0_462"
OpenJDK Runtime Environment (Alibaba Dragonwell Standard Edition 8.26.25) (build 1.8.0_462-b01)
OpenJDK 64-Bit Server VM (Alibaba Dragonwell Standard Edition 8.26.25) (build 25.462-b01, mixed mode)
openjdk version "11.0.28.24" 2025-07-15
OpenJDK Runtime Environment (Alibaba Dragonwell Standard Edition)-11.0.28.24+6-GA (build 11.0.28.24+6)
OpenJDK 64-Bit Server VM (Alibaba Dragonwell Standard Edition)-11.0.28.24+6-GA (build 11.0.28.24+6, mixed mode)
openjdk version "17.0.16" 2025-07-15
OpenJDK Runtime Environment (Alibaba Dragonwell Standard Edition)-17.0.16.0.17+8-GA (build 17.0.16+8)
OpenJDK 64-Bit Server VM (Alibaba Dragonwell Standard Edition)-17.0.16.0.17+8-GA (build 17.0.16+8, mixed mode, sharing)
openjdk version "21.0.8.0.8" 2025-07-15
OpenJDK Runtime Environment (Alibaba Dragonwell Standard Edition)-21.0.8.0.8+9-GA (build 21.0.8.0.8)
OpenJDK 64-Bit Server VM (Alibaba Dragonwell Standard Edition)-21.0.8.0.8+9-GA (build 21.0.8.0.8, mixed mode, sharing)
Execution environment
OS:
Operating System Name: Linux
Operating System Architecture: amd64
Operating System Version: 4.15.0-45-generic
Metadata
Metadata
Assignees
Labels
No labels