Consider a test case like this that has two lambdas
public class HelloLambda {
static Runnable r = () -> {
System.out.println("in runnable");
};
public static void main(String ... args) {
r.run();
if (args.length > 0) {
Runnable r2 = () -> {
System.out.println("in runnable2");
};
r2.run();
}
}
}
The AOT cache is created ike this:
$ java -cp HelloLambda.jar -XX:AOTMode=record \
-XX:AOTConfiguration=hl.aotconfig HelloLambda
$ java -cp HelloLambda.jar -XX:AOTMode=create \
-XX:AOTCache=hl.aot -XX:AOTConfiguration=hl.aotconfig
and executed in production run with an extra parameter
$ java -cp HelloLambda.jar -XX:AOTCache=hl.aot \
-XX:+TraceBytecodes -Xint \
HelloLambda XXX > trace.txt
$ grep 'java.lang.invoke.LambdaMetafactory[.]' trace.txt | head -10
[1985828] static jobject java.lang.invoke.LambdaMetafactory.metafactory(jobject, jobject, jobject, jobject, jobject, jobject)
[1985828] static jobject java.lang.invoke.LambdaMetafactory.metafactory(jobject, jobject, jobject, jobject, jobject, jobject)
[1985828] static jobject java.lang.invoke.LambdaMetafactory.metafactory(jobject, jobject, jobject, jobject, jobject, jobject)
[1985828] static jobject java.lang.invoke.LambdaMetafactory.metafactory(jobject, jobject, jobject, jobject, jobject, jobject)
[1985828] static jobject java.lang.invoke.LambdaMetafactory.metafactory(jobject, jobject, jobject, jobject, jobject, jobject)
[1985828] static jobject java.lang.invoke.LambdaMetafactory.metafactory(jobject, jobject, jobject, jobject, jobject, jobject)
[1985828] static jobject java.lang.invoke.LambdaMetafactory.metafactory(jobject, jobject, jobject, jobject, jobject, jobject)
[1985828] static jobject java.lang.invoke.LambdaMetafactory.metafactory(jobject, jobject, jobject, jobject, jobject, jobject)
[1985828] 229821 49 getstatic 23 <java/lang/invoke/LambdaMetafactory.EMPTY_CLASS_ARRAY:[Ljava/lang/Class;>
[1985828] static void java.lang.invoke.LambdaMetafactory.<clinit>()
Problem
=======
From the above log, you can see that quite a few bytecodes in a static method of LambdaMetafactory has been executed, before the LambdaMetafactory.<clinit> is executed.
Expected: LambdaMetafactory.<clinit> should have been executed before LambdaMetafactory.metafactory() is executed.
Analysis:
=======
When the HelloLambda class is stored into the AOT cache, the MethodHandle that refers to the LambdaMetafactory.metafactory BSM is AOT-resolved. However, the second invokedynamic instruction was not AOT-resolved (because it was not resolved during the training run).
As a result, when the invokedynamic instruction is executed during the production run, the JVM executes the BSM by invoking the already resolved MethodHandle. Such an invocation does not check for clinit state of the LambdaMetafactory class. (The clinit check was done when the MethodHandle was initialized, but that happened during the assembly phase).
Impact:
=====
This bug seems to be harmless as these are the only BSMs that are supported for aot-linking
StringConcatFactory::makeConcatWithConstants()
LambdaMetafactory::metafactory()
Both methods will do some simple operations and then load a static variable, which will cause the holder class to be initialized.
Proposed Fix
==========
If a MethodHandle or VarHandle H refers to a static member of a class C, but C is not known to be aot-initialized, we should add a class init barrier to H that will trigger the initialization of C before the static member is accessed.
public class HelloLambda {
static Runnable r = () -> {
System.out.println("in runnable");
};
public static void main(String ... args) {
r.run();
if (args.length > 0) {
Runnable r2 = () -> {
System.out.println("in runnable2");
};
r2.run();
}
}
}
The AOT cache is created ike this:
$ java -cp HelloLambda.jar -XX:AOTMode=record \
-XX:AOTConfiguration=hl.aotconfig HelloLambda
$ java -cp HelloLambda.jar -XX:AOTMode=create \
-XX:AOTCache=hl.aot -XX:AOTConfiguration=hl.aotconfig
and executed in production run with an extra parameter
$ java -cp HelloLambda.jar -XX:AOTCache=hl.aot \
-XX:+TraceBytecodes -Xint \
HelloLambda XXX > trace.txt
$ grep 'java.lang.invoke.LambdaMetafactory[.]' trace.txt | head -10
[1985828] static jobject java.lang.invoke.LambdaMetafactory.metafactory(jobject, jobject, jobject, jobject, jobject, jobject)
[1985828] static jobject java.lang.invoke.LambdaMetafactory.metafactory(jobject, jobject, jobject, jobject, jobject, jobject)
[1985828] static jobject java.lang.invoke.LambdaMetafactory.metafactory(jobject, jobject, jobject, jobject, jobject, jobject)
[1985828] static jobject java.lang.invoke.LambdaMetafactory.metafactory(jobject, jobject, jobject, jobject, jobject, jobject)
[1985828] static jobject java.lang.invoke.LambdaMetafactory.metafactory(jobject, jobject, jobject, jobject, jobject, jobject)
[1985828] static jobject java.lang.invoke.LambdaMetafactory.metafactory(jobject, jobject, jobject, jobject, jobject, jobject)
[1985828] static jobject java.lang.invoke.LambdaMetafactory.metafactory(jobject, jobject, jobject, jobject, jobject, jobject)
[1985828] static jobject java.lang.invoke.LambdaMetafactory.metafactory(jobject, jobject, jobject, jobject, jobject, jobject)
[1985828] 229821 49 getstatic 23 <java/lang/invoke/LambdaMetafactory.EMPTY_CLASS_ARRAY:[Ljava/lang/Class;>
[1985828] static void java.lang.invoke.LambdaMetafactory.<clinit>()
Problem
=======
From the above log, you can see that quite a few bytecodes in a static method of LambdaMetafactory has been executed, before the LambdaMetafactory.<clinit> is executed.
Expected: LambdaMetafactory.<clinit> should have been executed before LambdaMetafactory.metafactory() is executed.
Analysis:
=======
When the HelloLambda class is stored into the AOT cache, the MethodHandle that refers to the LambdaMetafactory.metafactory BSM is AOT-resolved. However, the second invokedynamic instruction was not AOT-resolved (because it was not resolved during the training run).
As a result, when the invokedynamic instruction is executed during the production run, the JVM executes the BSM by invoking the already resolved MethodHandle. Such an invocation does not check for clinit state of the LambdaMetafactory class. (The clinit check was done when the MethodHandle was initialized, but that happened during the assembly phase).
Impact:
=====
This bug seems to be harmless as these are the only BSMs that are supported for aot-linking
StringConcatFactory::makeConcatWithConstants()
LambdaMetafactory::metafactory()
Both methods will do some simple operations and then load a static variable, which will cause the holder class to be initialized.
Proposed Fix
==========
If a MethodHandle or VarHandle H refers to a static member of a class C, but C is not known to be aot-initialized, we should add a class init barrier to H that will trigger the initialization of C before the static member is accessed.
- causes
-
JDK-8352768 CDS test MethodHandleTest.java failed in -Xcomp mode
-
- In Progress
-
- links to
-
Commit(master) openjdk/jdk/adfb1206
-
Review(master) openjdk/jdk/24004