Closed 1a2s3d4f1 closed 4 years ago
I can confirm this. Happens on Linux as well as Windows.
openjdk version "1.8.0_252"
OpenJDK Runtime Environment (build 1.8.0_252-b09)
Eclipse OpenJ9 VM (build openj9-0.20.0, JRE 1.8.0 Linux amd64-64-Bit Compressed References 20200416_608 (JIT enabled, AOT enabled)
OpenJ9 - 05fa2d361
OMR - d4365f371
JCL - cfa47e66cd5 based on jdk8u252-b09)
openjdk version "11.0.7" 2020-04-14
OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.7+10)
Eclipse OpenJ9 VM AdoptOpenJDK (build openj9-0.20.0, JRE 11 Linux amd64-64-Bit Compressed References 20200416_574 (JIT enabled, AOT enabled)
OpenJ9 - 05fa2d361
OMR - d4365f371
JCL - 838028fc9d based on jdk-11.0.7+10)
Output of uname -a
:
Linux [computername] 5.6.19-158.current #1 SMP PREEMPT Sun Jul 26 14:17:01 UTC 2020 x86_64 GNU/Linux
openjdk version "1.8.0_252"
OpenJDK Runtime Environment (build 1.8.0_252-b09)
Eclipse OpenJ9 VM (build openj9-0.20.0, JRE 1.8.0 Windows 10 amd64-64-Bit Compressed References 20200422_667 (JIT enabled, AOT enabled)
OpenJ9 - 05fa2d361
OMR - d4365f371
JCL - 5e623848e9 based on jdk8u252-b09)
Same for me, details in case it helps (Debian Buster with latest apt updates):
$ java -version
openjdk version "1.8.0_265"
OpenJDK Runtime Environment (build 1.8.0_265-b01)
Eclipse OpenJ9 VM (build openj9-0.21.0, JRE 1.8.0 Linux amd64-64-Bit Compressed References 20200728_727 (JIT enabled, AOT enabled)
OpenJ9 - 34cf4c075
OMR - 113e54219
JCL - c82ff0c20f based on jdk8u265-b01)
$ uname -a
Linux amp-02 4.19.0-9-amd64 #1 SMP Debian 4.19.118-2+deb10u1 (2020-06-07) x86_64 GNU/Linux
Let me do an initial investigation to see what happened as it was more or less related to MethodHandle.
@1a2s3d4f1, could you provide the downloading link for the Minecraft (java edition) or share the jar file with us (e.g. via Google Drive). It seems the software is not free for downloading.
@ChengJin01 The server software is free to download and the stack trace is printed on there.
Here is the Minecraft version before any errors happened (1.16.2pre1): https://launcher.mojang.com/v1/objects/d4434bf4f2f0572a4eb54b3da1b1b3069a4e9ef2/server.jar
Here is the server jar where the errors start to occur (1.16.2pre2): https://launcher.mojang.com/v1/objects/d2cae287324631b2b4bfa609dd01c63cd6d4b78d/server.jar
I reproduced the issue on 1.16.2pre2
(1.16.2pre1
works fine with OpenJ9) with the latest nightly build on JDK11 with the following command to trigger dumps:
$ ../jdk11_openj9/bin/java -Xint -Xdump:java+system+snap:events=throw,filter=*NullPointerException -Xmx1024M -Xms1024M -jar server.jar
JVMDUMP039I Processing dump event "throw", detail "java/lang/NullPointerException" at 2020/08/12 17:39:14 - please wait.
...
JVMDUMP013I Processed dump event "throw", detail "java/lang/NullPointerException".
Exception in thread "main" java.lang.ExceptionInInitializerError
at java.base/java.lang.J9VMInternals.ensureError(J9VMInternals.java:186)
at java.base/java.lang.J9VMInternals.recordInitializationFailure(J9VMInternals.java:175)
at java.base/java.lang.invoke.MethodHandle.getCPMethodHandleAt(Native Method)
at java.base/java.lang.invoke.MethodHandle.getAdditionalBsmArg(MethodHandle.java:935)
at java.base/java.lang.invoke.MethodHandle.resolveInvokeDynamic(MethodHandle.java:1084)
at hk.<clinit>(SourceFile:50)
at gm.<clinit>(SourceFile:237)
at vm.a(SourceFile:42)
at net.minecraft.server.Main.main(SourceFile:88)
Caused by: java.lang.NullPointerException <-------
at hk.a(SourceFile:88)
at kk.a(SourceFile:20)
at kk.<clinit>(SourceFile:13)
... 7 more
Looking at the java core:
1XMCURTHDINFO Current thread
3XMTHREADINFO "main" J9VMThread:0x0000000000016700, omrthread_t:0x00007F44B001C690, java/lang/Thread:0x00000000F8004000, state:R, prio=5
3XMJAVALTHREAD (java/lang/Thread getId:0x1, isDaemon:false)
3XMTHREADINFO1 (native thread ID:0x1E3A, native priority:0x5, native policy:UNKNOWN, vmstate:R, vm thread flags:0x00001020)
3XMTHREADINFO2 (native stack address range from:0x00007F44B51AE000, to:0x00007F44B59AF000, size:0x801000)
3XMCPUTIME CPU usage total: 3.824697809 secs, current category="Application"
3XMHEAPALLOC Heap bytes allocated since last GC cycle=41066240 (0x2729F00)
3XMTHREADINFO3 Java callstack:
4XESTACKTRACE at hk.a(SourceFile:88)
4XESTACKTRACE at kk.a(SourceFile:20)
4XESTACKTRACE at kk.<clinit>(SourceFile:13)
4XESTACKTRACE at java/lang/invoke/MethodHandle.getCPMethodHandleAt(Native Method)
4XESTACKTRACE at java/lang/invoke/MethodHandle.getAdditionalBsmArg(MethodHandle.java:935)
4XESTACKTRACE at java/lang/invoke/MethodHandle.resolveInvokeDynamic(MethodHandle.java:1084)
4XESTACKTRACE at hk.<clinit>(SourceFile:50)
4XESTACKTRACE at gm.<clinit>(SourceFile:237)
4XESTACKTRACE at vm.a(SourceFile:42)
4XESTACKTRACE at net/minecraft/server/Main.main(SourceFile:88)
against the system core in DDR:
> !threads
!stack 0x00016700 !j9vmthread 0x00016700 !j9thread 0x7f44b001c690 tid 0x1e3a (7738) // (main)
...
> !stack 0x00016700
<16700> Generic special frame
<16700> !j9method 0x000000000053D018 hk.a(Lgm;Lvk;Ljava/lang/Object;)Ljava/lang/Object; <-----
<16700> !j9method 0x0000000000543668 kk.a(Lcoh;)Lcoh;
<16700> !j9method 0x00000000005436A8 kk.<clinit>()V
<16700> JNI call-in frame
<16700> !j9method 0x000000000002D020 java/lang/invoke/MethodHandle.getCPMethodHandleAt(Ljava/lang/Object;I)Ljava/lang/invoke/MethodHandle;
<16700> !j9method 0x000000000002D080 java/lang/invoke/MethodHandle.getAdditionalBsmArg(Lcom/ibm/oti/vm/VMLangAccess;Ljava/lang/Object;Ljava/lang/Class;Ljava/lang/invoke/MethodHandle;JII)Ljava/lang/Object;
<16700> !j9method 0x000000000002D100 java/lang/invoke/MethodHandle.resolveInvokeDynamic(JLjava/lang/String;Ljava/lang/String;J)Ljava/lang/invoke/MethodHandle;
...
> !stackslots 0x00016700
<16700> *** BEGIN STACK WALK, flags = 00400001 walkThread = 0x0000000000016700 ***
<16700> ITERATE_O_SLOTS
<16700> RECORD_BYTECODE_PC_OFFSET
<16700> Initial values: walkSP = 0x00000000000CEAE8, PC = 0x0000000000000001, literals = 0x0000000000000000, A0 = 0x00000000000CEB00, j2iFrame = 0x0000000000000000, ELS = 0x00007F44B59ABB70, decomp = 0x0000000000000000
<16700> Generic special frame: bp = 0x00000000000CEB00, sp = 0x00000000000CEAE8, pc = 0x0000000000000001, cp = 0x0000000000000000, arg0EA = 0x00000000000CEB00, flags = 0x0000000000000000
<16700> Bytecode frame: bp = 0x00000000000CEB18, sp = 0x00000000000CEB08, pc = 0x00007F44B0654539, cp = 0x0000000000541090, arg0EA = 0x00000000000CEB30, flags = 0x0000000000000000
<16700> Method: hk.a(Lgm;Lvk;Ljava/lang/Object;)Ljava/lang/Object; !j9method 0x000000000053D018 <---
<16700> Bytecode index = 5 <----------------
<16700> Using local mapper
<16700> Locals starting at 0x00000000000CEB30 for 0x0000000000000003 slots
<16700> I-Slot: a0[0x00000000000CEB30] = 0x0000000000000000 <----- the passed-in gm was NULL
<16700> O-Slot: a1[0x00000000000CEB28] = 0x00000000FA4C3338 <---- vk
<16700> O-Slot: a2[0x00000000000CEB20] = 0x00000000FA575828 <---- coh
> !j9object 0x00000000FA4C3338
!J9Object 0x00000000FA4C3338 {
struct J9Class* clazz = !j9class 0x453900 // vk
Object flags = 0x00000000;
I lockword = 0x00000000 (offset = 0) (java/lang/Object) <hidden>
Ljava/lang/String; b = !fj9object 0xc00b4fe8 (offset = 4) (vk)
Ljava/lang/String; c = !fj9object 0xc00ce408 (offset = 8) (vk)
}
> !j9object 0x00000000FA575828
!J9Object 0x00000000FA575828 {
struct J9Class* clazz = !j9class 0x544000 // coh
Object flags = 0x00000000;
I lockword = 0x00000000 (offset = 0) (java/lang/Object) <hidden>
Lvk; d = !fj9object 0xfa4c3338 (offset = 4) (coh)
Ljava/util/List; e = !fj9object 0xfa623918 (offset = 8) (coh)
Ljava/util/List; f = !fj9object 0xfa623930 (offset = 12) (coh)
Lvk; g = !fj9object 0xfa4c3338 (offset = 16) (coh)
I h = 0x80000000 (offset = 20) (coh)
> !bytecodes 0x000000000053D018
Name: a
Signature: (Lgm;Lvk;Ljava/lang/Object;)Ljava/lang/Object;
Access Flags (3040009): public static
Max Stack: 4
Argument Count: 3
Temp Count: 0
0 aload0
1 checkcast 16 gs
4 aload0
5 invokevirtual 22 gm.f()Lvj; <------- the null point exception was thrown out here due to the NULL value of gm
8 aload1
9 invokestatic 23 vj.a(Lvj;Lvk;)Lvj;
12 aload2
13 invokestatic 5 com/mojang/serialization/Lifecycle.stable()Lcom/mojang/serialization/Lifecycle;
16 invokevirtual 17 gs.a(Lvj;Ljava/lang/Object;Lcom/mojang/serialization/Lifecycle;)Ljava/lang/Object;
19 return1
<16700> Bytecode frame: bp = 0x00000000000CEB48, sp = 0x00000000000CEB38, pc = 0x00007F44B0656F70, cp = 0x0000000000543480, arg0EA = 0x00000000000CEB50, flags = 0x0000000000000000
<16700> Method: kk.a(Lcoh;)Lcoh; !j9method 0x0000000000543668
<16700> Bytecode index = 8
<16700> Using local mapper
<16700> Locals starting at 0x00000000000CEB50 for 0x0000000000000001 slots
<16700> I-Slot: a0[0x00000000000CEB50] = 0x00000000FA575828
> !bytecodes 0x0000000000543668
Name: a
Signature: (Lcoh;)Lcoh;
Access Flags (1040009): public static
Max Stack: 3
Argument Count: 1
Temp Count: 0
0 getstatic 6 hk.h Lgm; <----- gm was set to NULL here before calling hk.a()
3 aload0
4 invokevirtual 7 coh.b()Lvk;
7 aload0
8 invokestatic 8 hk.a(Lgm;Lvk;Ljava/lang/Object;)Ljava/lang/Object;
11 checkcast 4 coh
14 return1
Based on the investigation above, the NullPointerException issue with hk.a(Lgm;Lvk;Ljava/lang/Object;)Ljava/lang/Object;
was triggered by a passed-in NULL value for gm
, in which case there is nothing more we can do in OpenJ9 given that the source code involved above come from Minecraft.
So I think Minecraft developers need to get involved to double-check their code there to see why a NULL value for gm
was passed in hk.a()
at this point.
I believe it is an OpenJ9 issue. The affected code (mappings from FabricMC/yarn) is located within the static initializer (<clinit>
) of net.minecraft.util.registry.BuiltinRegistry
.
STRUCTURE_POOL = addRegistry(Registry.TEMPLATE_POOL_WORLDGEN, StructurePools::initDefaultPools);
BIOME = addRegistry(Registry.BIOME_KEY, () -> {
return Biomes.PLAINS;
});
CHUNK_GENERATOR_SETTINGS = addRegistry(Registry.NOISE_SETTINGS_WORLDGEN, ChunkGeneratorSettings::getInstance);
DEFAULT_VALUE_SUPPLIERS.forEach((identifier, supplier) -> {
if (supplier.get() == null) {
LOGGER.error("Unable to bootstrap registry '{}'", identifier);
}
});
The OpenJ9 VM will run the static initializer for the StructurePools
and ChunkGeneratorSettings
classes due to the method references, but those classes depend on BuiltinRegistry
to have already run its static initializer. This in turn causes a NullPointerException
. The HotSpot VM will not run the static initializers until the method references are actually called later on in the forEach
loop, which prevents the exception from occurring.
Edit: wrote a fabric mod that seems to fix it: https://github.com/iczero/fabric-openj9compat
@iczero, considering the difference of triggering the static initialization in implementation between OpenJ9 and Hotspot, I don't think we support the lazy static initialization in the existing code of OpenJ9.
The static initialization is triggered for the the specified class immediately if it has <clinit>
in our code. If we have to match Hotspot's behaviour in such case, technically we might need to move sendClinit
from initializeImpl() to somewhere in the interpreter which might break the original design of OpenJ9 in such case:
/runtime/vm/ClassInitialization.cpp
void
initializeImpl(J9VMThread *currentThread, J9Class *clazz)
{
...
if (J9ROMCLASS_HAS_CLINIT(clazz->romClass)) {
sendClinit(currentThread, clazz); <-------
}
@gacholio & @DanHeidinga, any input at this point in case of something I am unaware of?
Edit: wrote a fabric mod that seems to fix it: https://github.com/iczero/fabric-openj9compat
It works just fine with the mod. But this is a temporary solution and fixes should be worked on.
@iczero, is there any simple test code for us (or similar to the code in Minecraft) to demonstrate your findings in OpenJ9 as compared to Hotspot?
Based on JVM Spec, <clinit>
is called whenever a static field or method is accessed, or when an instance is created, in which case we follow the JVM Spec to trigger <clinit>
. So we'd like to have a simple test to see what really happened to our code.
This seems to work:
public class Test {
public static class Class1 {
public static void dummyMethod() {
System.out.println("Class1.dummyMethod: Class2.a is " + Class2.a);
}
static {
System.out.println("Class1.<clinit>: Class2.a is " + Class2.a);
}
}
public static class Class2 {
public static float a = 0;
public static Runnable runnable;
// dummy method to trigger <clinit>
public static void init() {}
static {
System.out.println("Class2.<clinit> called");
System.out.println("obtaining method reference to Class1.dummyMethod");
runnable = Class1::dummyMethod;
System.out.println("incrementing Class2.a");
a++;
System.out.println("running Class1.dummyMethod via method reference");
runnable.run();
}
}
public static void main(String[] args) {
Class2.init();
}
}
Output on HotSpot:
Class2.<clinit> called
obtaining method reference to Class1.dummyMethod
incrementing Class2.a
running Class1.dummyMethod via method reference
Class1.<clinit>: Class2.a is 1.0
Class1.dummyMethod: Class2.a is 1.0
Output on OpenJ9:
Class2.<clinit> called
obtaining method reference to Class1.dummyMethod
Class1.<clinit>: Class2.a is 0.0
incrementing Class2.a
running Class1.dummyMethod via method reference
Class1.dummyMethod: Class2.a is 1.0
The Class1::dummyMethod
produces this:
20 JBinvokedynamic 0 run()Ljava/lang/Runnable; bsm 0 java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
We may well be initializing the class too early in the MethodHandle code.
@DanHeidinga - you may recall exactly where this is done.
If so, it might be related to the code as follows:
/runtime/vm/MHInterpreter.cpp
VM_MHInterpreter::dispatchLoop(j9object_t methodHandle)
{
...
case J9_METHOD_HANDLE_KIND_STATIC: {
methodHandle = initializeClassIfNeeded(methodHandle); <-------
/* fall through */
}
VMINLINE j9object_t
initializeClassIfNeeded(j9object_t methodHandle)
{
J9Class *defc = getPrimitiveHandleDefc(methodHandle);
if (VM_VMHelpers::classRequiresInitialization(_currentThread, defc)) { <------------
/* Build frame, make pointers A0-relative offsets, and save methodHandle */
UDATA *spPriorToFrameBuild = _currentThread->sp;
J9SFMethodTypeFrame *frame = buildMethodTypeFrame(_currentThread, getMethodHandleMethodType(methodHandle));
IDATA spOffset = spPriorToFrameBuild - _currentThread->arg0EA;
IDATA frameOffset = (UDATA*)frame - _currentThread->arg0EA;
VM_VMHelpers::pushObjectInSpecialFrame(_currentThread, methodHandle);
/* Call defc.<clinit> */
initializeClass(_currentThread, defc); <------
/**
* Checks whether the class must be initialized before using it.
*
* Classes which have been successfully initialized or are in the process
* of being initialized by the current thread do not need to be initialized.
*
* @param currentThread[in] the current J9VMThread
* @param j9clazz[in] the J9Class to query
*
* @returns true if the class requires initialization, false if not
*/
static VMINLINE bool
classRequiresInitialization(J9VMThread *currentThread, J9Class *j9clazz) <---------
{
bool requiresInitialization = true;
UDATA initStatus = j9clazz->initializeStatus;
if ((J9ClassInitSucceeded == initStatus) || (((UDATA)currentThread) == initStatus)) { <------
requiresInitialization = false;
}
return requiresInitialization;
}
Please start investigating moving the initialization to the point of static method invoke, static field read/write and instantiation (if there is such a thing in the MH code).
Handles should operate like the bytecodes they replace - until the actual point of access, the class should remain uninitialized.
It's probably also worth investigating the old reflection behaviour - I don't recall if it's specced to initalize the class on lookup or access.
The MH code should be doing the right thing for initializing classes when static methods / static fields / etc are invoked.
Based on the output from the <clinit>
printfs, I'd check the sendResolveMethodHandle and sendResolveInvokeDynamic paths as well the code that cracks a MethodHandle into a MethodHandleInfo. It's something in that path that's forcing the init - maybe a class lookup as part of building a MethodTyoe?
@cheng Any update here?
@gacholio,
I went through the stacktrace on the calling path to Test$Class1.<clinit>
:
Thread 2 "main" hit Breakpoint 1, VM_MHInterpreter::dispatchLoop (this=0x7ffff7d443d0, methodHandle=0x7fff827b8)
at MHInterpreter.cpp:148
148 methodHandle = initializeClassIfNeeded(methodHandle);
(gdb) bt
#0 VM_MHInterpreter::dispatchLoop (this=0x7ffff7d443d0, methodHandle=0x7fff827b8) at MHInterpreter.cpp:148
#1 0x00007ffff72a5b7e in VM_BytecodeInterpreterCompressed::interpretMethodHandle (this=0x7ffff7d449d0,
_sp=@0x7ffff7d44940: 0xceba8, _pc=@0x7ffff7d44948: 0x7fffd19d5122 <incomplete sequence \351\251>, methodHandle=0x7fff91c78)
at BytecodeInterpreter.hpp:169
#2 0x00007ffff72c78a7 in VM_BytecodeInterpreterCompressed::run (this=0x7ffff7d449d0, vmThread=0x16700)
at BytecodeInterpreter.hpp:9554
#3 0x00007ffff729c0e5 in bytecodeLoopCompressed (currentThread=0x16700) at BytecodeInterpreter.inc:110
#4 0x00007ffff73dddd2 in c_cInterpreter () at xcinterp.s:160
#5 0x00007ffff7364c15 in sendResolveInvokeDynamic (currentThread=0x16700, ramCP=0x13e220, callSiteIndex=1,
nameAndSig=0x7ffff4129670, bsmData=0x7ffff4129746) at callin.cpp:1029
#6 0x00007ffff73c2a5a in resolveInvokeDynamic (vmThread=0x16700, ramCP=0x13e220, callSiteIndex=1, resolveFlags=0)
at resolvesupport.cpp:2127
#7 0x00007ffff72ba8e9 in VM_BytecodeInterpreterCompressed::invokedynamic (this=0x7ffff7d45320, _sp=@0x7ffff7d45290: 0xcecf0,
_pc=@0x7ffff7d45298: 0x1 <error: Cannot access memory at address 0x1>) at BytecodeInterpreter.hpp:8030
#8 0x00007ffff72dbc57 in VM_BytecodeInterpreterCompressed::run (this=0x7ffff7d45320, vmThread=0x16700)
at BytecodeInterpreter.hpp:10295
#9 0x00007ffff729c0e5 in bytecodeLoopCompressed (currentThread=0x16700) at BytecodeInterpreter.inc:110
#10 0x00007ffff73dddd2 in c_cInterpreter () at xcinterp.s:160
#11 0x00007ffff7361d17 in sendClinit (currentThread=0x16700, clazz=0x13e300) at callin.cpp:419
#12 0x00007ffff72de5d2 in initializeImpl (currentThread=0x16700, clazz=0x13e300) at ClassInitialization.cpp:120
#13 0x00007ffff72e0aeb in classInitStateMachine (currentThread=0x16700, clazz=0x13e300, desiredState=J9_CLASS_INIT_INITIALIZED)
at ClassInitialization.cpp:763
#14 0x00007ffff72df420 in initializeClass (currentThread=0x16700, clazz=0x13e300) at ClassInitialization.cpp:335
#15 0x00007ffff73bda71 in resolveStaticMethodRefInto (vmStruct=0x16700, ramCP=0x13e0b0, cpIndex=20, resolveFlags=0,
ramCPEntry=0x167f8) at resolvesupport.cpp:533 <--------------------
#16 0x00007ffff73bdb7a in resolveStaticMethodRef (vmStruct=0x16700, ramCP=0x13e0b0, cpIndex=20, resolveFlags=0)
at resolvesupport.cpp:568
#17 0x00007ffff73c1d08 in resolveMethodHandleRefInto (vmThread=0x16700, ramCP=0x13e0b0, cpIndex=17, resolveFlags=0,
ramCPEntry=0x13e1c0) at resolvesupport.cpp:1904
#18 0x00007ffff73c1ff7 in resolveMethodHandleRef (vmThread=0x16700, ramCP=0x13e0b0, cpIndex=17, resolveFlags=0)
at resolvesupport.cpp:1976
#19 0x00007ffff618a049 in Java_java_lang_invoke_MethodHandle_getCPMethodHandleAt (env=0x16700, unusedClass=0x2d630,
constantPoolOop=0xced98, cpIndex=17) at common/sun_reflect_ConstantPool.c:755
#20 0x00007ffff7475fe8 in ffi_call_unix64 () at x86/unix64.S:76
#21 0x00007ffff74755d0 in ffi_call (cif=0x7ffff7d45ab0, fn=0x7ffff6189f14 <Java_java_lang_invoke_MethodHandle_getCPMethodHandleAt>,
rvalue=0x16818, avalue=0x7ffff7d45b50) at x86/ffi64.c:525
#22 0x00007ffff72ab60a in VM_BytecodeInterpreterCompressed::cJNICallout (this=0x7ffff7d46400, _sp=@0x7ffff7d46370: 0xced68,
_pc=@0x7ffff7d46378: 0x7 <error: Cannot access memory at address 0x7>, receiverAddress=0x2d630, javaArgs=0xceda0,
returnType=0x7ffff7d45d43 "\t", returnStorage=0x16818,
function=0x7ffff6189f14 <Java_java_lang_invoke_MethodHandle_getCPMethodHandleAt>, isStatic=true) at BytecodeInterpreter.hpp:2436
#23 0x00007ffff72ab011 in VM_BytecodeInterpreterCompressed::callCFunction (this=0x7ffff7d46400, _sp=@0x7ffff7d46370: 0xced68,
_pc=@0x7ffff7d46378: 0x7 <error: Cannot access memory at address 0x7>,
jniMethodStartAddress=0x7ffff6189f14 <Java_java_lang_invoke_MethodHandle_getCPMethodHandleAt>, receiverAddress=0x2d630,
javaArgs=0xced98, bp=0x7ffff7d45d50, isStatic=true, returnType=0x7ffff7d45d43 "\t") at BytecodeInterpreter.hpp:2276
#24 0x00007ffff72aa85e in VM_BytecodeInterpreterCompressed::runJNINative (this=0x7ffff7d46400, _sp=@0x7ffff7d46370: 0xced68,
_pc=@0x7ffff7d46378: 0x7 <error: Cannot access memory at address 0x7>) at BytecodeInterpreter.hpp:2168
#25 0x00007ffff72c76aa in VM_BytecodeInterpreterCompressed::run (this=0x7ffff7d46400, vmThread=0x16700)
at BytecodeInterpreter.hpp:9537
#26 0x00007ffff729c0e5 in bytecodeLoopCompressed (currentThread=0x16700) at BytecodeInterpreter.inc:110
#27 0x00007ffff73dddd2 in c_cInterpreter () at xcinterp.s:160
#28 0x00007ffff7364c15 in sendResolveInvokeDynamic (currentThread=0x16700, ramCP=0x13e0b0, callSiteIndex=0,
nameAndSig=0x7ffff4129358, bsmData=0x7ffff4129466) at callin.cpp:1029
#29 0x00007ffff73c2a5a in resolveInvokeDynamic (vmThread=0x16700, ramCP=0x13e0b0, callSiteIndex=0, resolveFlags=0)
at resolvesupport.cpp:2127
#30 0x00007ffff72ba8e9 in VM_BytecodeInterpreterCompressed::invokedynamic (this=0x7ffff7d46d50, _sp=@0x7ffff7d46cc0: 0xcef38,
_pc=@0x7ffff7d46cc8: 0x1 <error: Cannot access memory at address 0x1>) at BytecodeInterpreter.hpp:8030
#31 0x00007ffff72dbc57 in VM_BytecodeInterpreterCompressed::run (this=0x7ffff7d46d50, vmThread=0x16700)
at BytecodeInterpreter.hpp:10295
#32 0x00007ffff729c0e5 in bytecodeLoopCompressed (currentThread=0x16700) at BytecodeInterpreter.inc:110
#33 0x00007ffff73dddd2 in c_cInterpreter () at xcinterp.s:160
#34 0x00007ffff7361d17 in sendClinit (currentThread=0x16700, clazz=0x13df00) at callin.cpp:419
#35 0x00007ffff72de5d2 in initializeImpl (currentThread=0x16700, clazz=0x13df00) at ClassInitialization.cpp:120
#36 0x00007ffff72e0aeb in classInitStateMachine (currentThread=0x16700, clazz=0x13df00, desiredState=J9_CLASS_INIT_INITIALIZED)
at ClassInitialization.cpp:763
#37 0x00007ffff72df420 in initializeClass (currentThread=0x16700, clazz=0x13df00) at ClassInitialization.cpp:335
#38 0x00007ffff73bda71 in resolveStaticMethodRefInto (vmStruct=0x16700, ramCP=0x13ddb0, cpIndex=2, resolveFlags=32,
ramCPEntry=0x167f8) at resolvesupport.cpp:533 <------ Test$Class2.<clinit>()V
#39 0x00007ffff73bdb7a in resolveStaticMethodRef (vmStruct=0x16700, ramCP=0x13ddb0, cpIndex=2, resolveFlags=32)
at resolvesupport.cpp:568
#40 0x00007ffff72a9c04 in VM_BytecodeInterpreterCompressed::initialStaticMethod (this=0x7ffff7d47810, _sp=@0x7ffff7d47780: 0xcefa0,
_pc=@0x7ffff7d47788: 0x1 <error: Cannot access memory at address 0x1>) at BytecodeInterpreter.hpp:1927
#41 0x00007ffff72be4d7 in VM_BytecodeInterpreterCompressed::run (this=0x7ffff7d47810, vmThread=0x16700)
--Type <RET> for more, q to quit, c to continue without paging--
at BytecodeInterpreter.hpp:9242
#42 0x00007ffff729c0e5 in bytecodeLoopCompressed (currentThread=0x16700) at BytecodeInterpreter.inc:110
#43 0x00007ffff73dddd2 in c_cInterpreter () at xcinterp.s:160
#44 0x00007ffff7365002 in runCallInMethod (env=0x16700, receiver=0x0, clazz=0xcf020, methodID=0x7ffff0201508, args=0x7ffff7d47d78)
at callin.cpp:1083
#45 0x00007ffff7387f44 in gpProtectedRunCallInMethod (entryArg=0x7ffff7d47d10) at jnicsup.cpp:259
#46 0x00007ffff73edc2e in signalProtectAndRunGlue (portLibrary=0x7ffff7544e00 <j9portLibrary>, userData=0x7ffff7d47cc0)
at jniprotect.c:45
#47 0x00007ffff70ac647 in omrsig_protect (portLibrary=0x7ffff7544e00 <j9portLibrary>, fn=0x7ffff73edbfe <signalProtectAndRunGlue>,
fn_arg=0x7ffff7d47cc0, handler=0x7ffff73804f8 <structuredSignalHandler>, handler_arg=0x16700, flags=506, result=0x7ffff7d47ca8)
at ../../omr/port/unix/omrsignal.c:425
#48 0x00007ffff73edd97 in gpProtectAndRun (function=0x7ffff7387ee3 <gpProtectedRunCallInMethod(void*)>, env=0x16700,
args=0x7ffff7d47d10) at jniprotect.c:78
#49 0x00007ffff73884eb in gpCheckCallin (env=0x16700, receiver=0x0, cls=0xcf020, methodID=0x7ffff0201508, args=0x7ffff7d47d78)
at jnicsup.cpp:447
#50 0x00007ffff7387238 in callStaticVoidMethod (env=0x16700, cls=0xcf020, methodID=0x7ffff0201508) at jnicgen.c:288
<----- Test.main([Ljava/lang/String;)V
#51 0x00007ffff7fb8016 in JavaMain (_args=0x7fffffffab90)
at /home/jincheng/X86_64_OPENJ9/openj9-openjdk-jdk11/src/java.base/share/native/libjli/java.c:549
#52 0x00007ffff7d70609 in start_thread (arg=<optimized out>) at pthread_create.c:477
#53 0x00007ffff7ece103 in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95
(gdb) frame 15
#15 0x00007ffff73bda71 in resolveStaticMethodRefInto (vmStruct=0x16700, ramCP=0x13e0b0, cpIndex=20, resolveFlags=0,
ramCPEntry=0x167f8) at resolvesupport.cpp:533
533 initializeClass(vmStruct, methodClass);
(gdb) list
528 if (initStatus != J9ClassInitSucceeded && initStatus != (UDATA)vmStruct) {
529 /* Initialize the class if java code is allowed */
530 if (canRunJavaCode) {
531 UDATA preCount = vmStruct->javaVM->hotSwapCount;
532
533 initializeClass(vmStruct, methodClass);
534 if (threadEventsPending(vmStruct)) {
535 method = NULL;
536 goto done;
537 }
(gdb) frame 15
#15 0x00007ffff73bda71 in resolveStaticMethodRefInto (vmStruct=0x16700, ramCP=0x13e0b0, cpIndex=20, resolveFlags=0,
ramCPEntry=0x167f8) at resolvesupport.cpp:533
533 initializeClass(vmStruct, methodClass);
(gdb) info locals
preCount = 0
methodClass = 0x13e300
initStatus = 3 <----------------- J9ClassInitUnverified 0x3
romMethodRef = 0x7ffff4129308
resolvedClass = 0x13e300
plus the corresponding java stacktrace as follows:
> !stack 0x00016700
<16700> !j9method 0x000000000002D0A0 java/lang/invoke/MethodHandle.invokeBsm(Ljava/lang/invoke/MethodHandle;[Ljava/lang/Object;)Ljava/lang/Object;
<16700> !j9method 0x000000000002D100 java/lang/invoke/MethodHandle.resolveInvokeDynamic(JLjava/lang/String;Ljava/lang/String;J)Ljava/lang/invoke/MethodHandle;
<16700> JNI call-in frame
<16700> Generic special frame
<16700> !j9method 0x000000000013E4F8 Test$Class1.<clinit>()V
<16700> JNI call-in frame
<16700> !j9method 0x000000000002D020 java/lang/invoke/MethodHandle.getCPMethodHandleAt(Ljava/lang/Object;I)Ljava/lang/invoke/MethodHandle;
<16700> !j9method 0x000000000002D080 java/lang/invoke/MethodHandle.getAdditionalBsmArg(Lcom/ibm/oti/vm/VMLangAccess;Ljava/lang/Object;Ljava/lang/Class;Ljava/lang/invoke/MethodHandle;JII)Ljava/lang/Object;
<16700> !j9method 0x000000000002D100 java/lang/invoke/MethodHandle.resolveInvokeDynamic(JLjava/lang/String;Ljava/lang/String;J)Ljava/lang/invoke/MethodHandle; <----------
<16700> JNI call-in frame
<16700> Generic special frame
<16700> !j9method 0x000000000013DE98 Test$Class2.<clinit>()V
<16700> JNI call-in frame
<16700> Generic special frame
<16700> !j9method 0x000000000013DE28 Test.main([Ljava/lang/String;)V
<16700> JNI call-in frame
<16700> Native method frame
It seems the static initialization of Test$Class1.<clinit>()V
came from the same place as Test$Class2.<clinit>()V
in resolveStaticMethodRefInto
at /runtime/vm/resolvesupport.cpp`
J9Method *
resolveStaticMethodRefInto(J9VMThread *vmStruct, J9ConstantPool *ramCP, UDATA cpIndex, UDATA resolveFlags, J9RAMStaticMethodRef *ramCPEntry)
{
...
method = (J9Method *)javaLookupMethod(vmStruct, resolvedClass, J9ROMMETHODREF_NAMEANDSIGNATURE(romMethodRef), cpClass, lookupOptions);
Trc_VM_resolveStaticMethodRef_lookupMethod(vmStruct, method);
if (method == NULL) {
goto done;
}
...
/* Initialize the defining class of the method. */
if (J9_ARE_NO_BITS_SET(resolveFlags, J9_RESOLVE_FLAG_NO_CLASS_INIT)) {
J9Class *methodClass = J9_CLASS_FROM_METHOD(method);
UDATA initStatus = methodClass->initializeStatus;
if (jitCompileTimeResolve && (J9ClassInitSucceeded != initStatus)) {
method = NULL;
goto done;
}
if (initStatus != J9ClassInitSucceeded && initStatus != (UDATA)vmStruct) { <-------------
/* Initialize the class if java code is allowed */
if (canRunJavaCode) {
UDATA preCount = vmStruct->javaVM->hotSwapCount;
initializeClass(vmStruct, methodClass); <-------- calling Test$Class2.<clinit> and later Test$Class1.<clinit>from there
if (threadEventsPending(vmStruct)) {
method = NULL;
goto done;
}
I didn't notice there is anything illogical along the calling path in dealing with <clinit>
of Test$Class2
and later with <clinit>
of Test$Class1
(we didn't force to initilze Test$Class1
given it was based on the value of initStatus
)
The problem with this test case above is there is dependency between Test$Class1.<clinit>
and Test$Class2.<clinit>
, in which case 1Test$Class2.requires
Test$Class1to be initilized first but
Test$Class1.
From the code perspective, if we prevent initiliazating Test$Class1
in resolveStaticMethodRefInto
, it also disable Test$Class2.<clinit>
at the same place.
It seems the only difference between Test$Class1.<clinit>
and Test$Class2.<clinit>
is the value of resolveFlags
given it was set up on different calling paths as follows:
[1] Test$Class1.J9_RESOLVE_FLAG_RUNTIME_RESOLVE
#14 0x00007ffff72df420 in initializeClass (currentThread=0x16700, clazz=0x13e300) at ClassInitialization.cpp:335
#15 0x00007ffff73bda71 in resolveStaticMethodRefInto (vmStruct=0x16700, ramCP=0x13e0b0, cpIndex=20, resolveFlags=0, <------
ramCPEntry=0x167f8) at resolvesupport.cpp:533
#16 0x00007ffff73bdb7a in resolveStaticMethodRef (vmStruct=0x16700, ramCP=0x13e0b0, cpIndex=20, resolveFlags=0)
at resolvesupport.cpp:568
#17 0x00007ffff73c1d08 in resolveMethodHandleRefInto (vmThread=0x16700, ramCP=0x13e0b0, cpIndex=17, resolveFlags=0,
ramCPEntry=0x13e1c0) at resolvesupport.cpp:1904
#18 0x00007ffff73c1ff7 in resolveMethodHandleRef (vmThread=0x16700, ramCP=0x13e0b0, cpIndex=17, resolveFlags=0)
at resolvesupport.cpp:1976
#19 0x00007ffff618a049 in Java_java_lang_invoke_MethodHandle_getCPMethodHandleAt (env=0x16700, unusedClass=0x2d630,
constantPoolOop=0xced98, cpIndex=17) at common/sun_reflect_ConstantPool.c:755 <-------
against the code at common/sun_reflect_ConstantPool.c
:
Java_java_lang_invoke_MethodHandle_getCPMethodHandleAt(JNIEnv *env, jclass unusedClass, jobject constantPoolOop, jint cpIndex)
{
...
if (NULL == methodHandleObject) {
---> methodHandleObject = vmFunctions->resolveMethodHandleRef(vmThread, J9_CP_FROM_CLASS(ramClass), cpIndex, J9_RESOLVE_FLAG_RUNTIME_RESOLVE);
}
[2] Test$Class2.J9_RESOLVE_FLAG_RUNTIME_RESOLVE | J9_RESOLVE_FLAG_CHECK_CLINIT
#37 0x00007ffff72df420 in initializeClass (currentThread=0x16700, clazz=0x13df00) at ClassInitialization.cpp:335
#38 0x00007ffff73bda71 in resolveStaticMethodRefInto (vmStruct=0x16700, ramCP=0x13ddb0, cpIndex=2, resolveFlags=32, <------
ramCPEntry=0x167f8) at resolvesupport.cpp:533
#39 0x00007ffff73bdb7a in resolveStaticMethodRef (vmStruct=0x16700, ramCP=0x13ddb0, cpIndex=2, resolveFlags=32)
at resolvesupport.cpp:568
#40 0x00007ffff72a9c04 in VM_BytecodeInterpreterCompressed::initialStaticMethod (this=0x7ffff7d47810, _sp=@0x7ffff7d47780: 0xcefa0,
_pc=@0x7ffff7d47788: 0x1 <error: Cannot access memory at address 0x1>) at BytecodeInterpreter.hpp:1927
against the code at BytecodeInterpreter.hpp
:
initialStaticMethod(REGISTER_ARGS_LIST)
{
VM_BytecodeAction rc = GOTO_RUN_METHOD;
bool split = (JBinvokestaticsplit == _pc[0]);
U_16 index = *(U_16*)(_pc + 1);
J9ConstantPool *ramConstantPool = J9_CP_FROM_METHOD(_literals);
buildGenericSpecialStackFrame(REGISTER_ARGS, 0);
updateVMStruct(REGISTER_ARGS);
if (split) {
_sendMethod = resolveStaticSplitMethodRef(_currentThread, ramConstantPool, index, J9_RESOLVE_FLAG_RUNTIME_RESOLVE | J9_RESOLVE_FLAG_CHECK_CLINIT);
} else {
---> _sendMethod = resolveStaticMethodRef(_currentThread, ramConstantPool, index, J9_RESOLVE_FLAG_RUNTIME_RESOLVE | J9_RESOLVE_FLAG_CHECK_CLINIT);
}
To differentiate these two kinds of static initialization method, we should check the value (J9_RESOLVE_FLAG_CHECK_CLINIT
) of resolveFlags
in resolveStaticMethodRefInto
so as to stop initializing Test$Class1
from Java_java_lang_invoke_MethodHandle_getCPMethodHandleAt
as follows:
/* Initialize the defining class of the method. */
if (J9_ARE_NO_BITS_SET(resolveFlags, J9_RESOLVE_FLAG_NO_CLASS_INIT)
&& J9_ARE_ALL_BITS_SET(resolveFlags, J9_RESOLVE_FLAG_RUNTIME_RESOLVE | J9_RESOLVE_FLAG_CHECK_CLINIT) <------
) {
J9Class *methodClass = J9_CLASS_FROM_METHOD(method);
UDATA initStatus = methodClass->initializeStatus;
if (jitCompileTimeResolve && (J9ClassInitSucceeded != initStatus)) {
method = NULL;
goto done;
}
if (initStatus != J9ClassInitSucceeded && initStatus != (UDATA)vmStruct) {
/* Initialize the class if java code is allowed */
if (canRunJavaCode) {
UDATA preCount = vmStruct->javaVM->hotSwapCount;
initializeClass(vmStruct, methodClass);
Test results indicate the proposed fix above works good as expected
../jdk11_openj9_mh_debug_v3/bin/java Test
Class2.<clinit> called
obtaining method reference to Class1.dummyMethod
incrementing Class2.a
running Class1.dummyMethod via method reference
Class1.<clinit>: Class2.a is 1.0
Class1.dummyMethod: Class2.a is 1.0
1.16.2pre1$ ../jdk11_openj9_mh_debug_v3/bin/java -Xmx1024M -Xms1024M -jar server.jar
[02:08:54] [main/INFO]: Environment: authHost='https://authserver.mojang.com', accountsHost='https://api.mojang.com', sessionHost='https://sessionserver.mojang.com', name='PROD'
[02:08:56] [main/WARN]: Ambiguity between arguments [teleport, destination] and [teleport, targets] with inputs: [Player, 0123, @e, dd12be42-52a9-4a91-a8a1-11c01849e498]
[02:08:56] [main/WARN]: Ambiguity between arguments [teleport, location] and [teleport, destination] with inputs: [0.1 -0.5 .9, 0 0 0]
[02:08:56] [main/WARN]: Ambiguity between arguments [teleport, location] and [teleport, targets] with inputs: [0.1 -0.5 .9, 0 0 0]
[02:08:56] [main/WARN]: Ambiguity between arguments [teleport, targets] and [teleport, destination] with inputs: [Player, 0123, dd12be42-52a9-4a91-a8a1-11c01849e498]
[02:08:56] [main/WARN]: Ambiguity between arguments [teleport, targets, location] and [teleport, targets, destination] with inputs: [0.1 -0.5 .9, 0 0 0]
[02:08:56] [main/INFO]: Reloading ResourceManager: Default
[02:08:59] [Worker-Main-5/INFO]: Loaded 7 recipes
[02:09:00] [Worker-Main-5/INFO]: Loaded 927 advancements
[02:09:06] [Server thread/INFO]: Starting minecraft server version 1.16.2 Pre-release 1
[02:09:06] [Server thread/INFO]: Loading properties
[02:09:06] [Server thread/INFO]: Default game type: SURVIVAL
[02:09:06] [Server thread/INFO]: Generating keypair
[02:09:07] [Server thread/INFO]: Starting Minecraft server on *:25565
[02:09:07] [Server thread/INFO]: Using epoll channel type
[02:09:07] [Server thread/INFO]: Preparing level "world"
[02:09:08] [Server thread/WARN]: Unable to find spawn biome
[02:09:18] [Server thread/INFO]: Preparing start region for dimension minecraft:overworld
[02:09:18] [Worker-Main-5/INFO]: Preparing spawn area: 2%
[02:09:18] [Worker-Main-4/INFO]: Preparing spawn area: 2%
[02:09:19] [Worker-Main-6/INFO]: Preparing spawn area: 2%
[02:09:19] [Worker-Main-6/INFO]: Preparing spawn area: 2%
...
[02:09:43] [Worker-Main-6/INFO]: Preparing spawn area: 91%
[02:09:43] [Server thread/INFO]: Time elapsed: 25499 ms
[02:09:43] [Server thread/INFO]: Done (36.357s)! For help, type "help"
1.16.2pre2$ ../jdk11_openj9_mh_debug_v3/bin/java -Xmx1024M -Xms1024M -jar server.jar
[02:11:41] [main/INFO]: Environment: authHost='https://authserver.mojang.com', accountsHost='https://api.mojang.com', sessionHost='https://sessionserver.mojang.com', name='PROD'
[02:11:43] [main/WARN]: Ambiguity between arguments [teleport, destination] and [teleport, targets] with inputs: [Player, 0123, @e, dd12be42-52a9-4a91-a8a1-11c01849e498]
[02:11:43] [main/WARN]: Ambiguity between arguments [teleport, location] and [teleport, destination] with inputs: [0.1 -0.5 .9, 0 0 0]
[02:11:43] [main/WARN]: Ambiguity between arguments [teleport, location] and [teleport, targets] with inputs: [0.1 -0.5 .9, 0 0 0]
[02:11:43] [main/WARN]: Ambiguity between arguments [teleport, targets] and [teleport, destination] with inputs: [Player, 0123, dd12be42-52a9-4a91-a8a1-11c01849e498]
[02:11:43] [main/WARN]: Ambiguity between arguments [teleport, targets, location] and [teleport, targets, destination] with inputs: [0.1 -0.5 .9, 0 0 0]
[02:11:43] [main/INFO]: Reloading ResourceManager: Default
[02:11:44] [Worker-Main-5/INFO]: Loaded 7 recipes
[02:11:45] [Worker-Main-5/INFO]: Loaded 927 advancements
[02:11:48] [Server thread/INFO]: Starting minecraft server version 1.16.2 Pre-release 2
[02:11:48] [Server thread/INFO]: Loading properties
[02:11:48] [Server thread/INFO]: Default game type: SURVIVAL
[02:11:48] [Server thread/INFO]: Generating keypair
[02:11:49] [Server thread/INFO]: Starting Minecraft server on *:25565
[02:11:49] [Server thread/INFO]: Using epoll channel type
[02:11:49] [Server thread/INFO]: Preparing level "world"
[02:11:59] [Server thread/INFO]: Preparing start region for dimension minecraft:overworld
[02:12:00] [Worker-Main-5/INFO]: Preparing spawn area: 0%
[02:12:00] [Worker-Main-4/INFO]: Preparing spawn area: 0%
[02:12:02] [Worker-Main-6/INFO]: Preparing spawn area: 0%
[02:12:02] [Worker-Main-4/INFO]: Preparing spawn area: 0%
[02:12:02] [Worker-Main-5/INFO]: Preparing spawn area: 0%
[02:12:02] [Worker-Main-5/INFO]: Preparing spawn area: 0%
[02:12:02] [Worker-Main-5/INFO]: Preparing spawn area: 0%
[02:12:02] [Worker-Main-6/INFO]: Preparing spawn area: 0%
[02:12:03] [Worker-Main-5/INFO]: Preparing spawn area: 0%
[02:12:03] [Worker-Main-6/INFO]: Preparing spawn area: 0%
[02:12:04] [Worker-Main-5/INFO]: Preparing spawn area: 1%
[02:12:04] [Worker-Main-4/INFO]: Preparing spawn area: 3%
[02:12:05] [Worker-Main-4/INFO]: Preparing spawn area: 3%
[02:12:05] [Worker-Main-4/INFO]: Preparing spawn area: 3%
...
[02:12:33] [Worker-Main-6/INFO]: Preparing spawn area: 96%
[02:12:33] [Worker-Main-4/INFO]: Preparing spawn area: 99%
[02:12:34] [Server thread/INFO]: Time elapsed: 34594 ms
[02:12:34] [Server thread/INFO]: Done (44.469s)! For help, type "help"
@ChengJin01 Nice find tracking down the path causing the resolve to request the <clinit>
. The proposed fix isn't the right approach though as that flag means something else - basically whether the check is happening in a <clinit>
method or not.
If the MH resolves shouldn't init the class, then it might be better to pass J9_RESOLVE_FLAG_NO_CLASS_INIT
either from the Java_java_lang_invoke_MethodHandle_getCPMethodHandleAt
call or from resolveMethodHandleRefInto
when calling the other resolve helpers (ie: resolveStaticMethodRef
)
@DanHeidinga, J9_RESOLVE_FLAG_NO_CLASS_INIT
makes more sense to me rather than checking J9_RESOLVE_FLAG_CHECK_CLINIT
(which means different thing I was unaware of).
In addition, setting J9_RESOLVE_FLAG_NO_CLASS_INIT
to resolveMethodHandleRef
from Java_java_lang_invoke_MethodHandle_getCPMethodHandleAt
might be better as we already have similar code to set up J9_RESOLVE_FLAG_NO_CLASS_INIT
in the interperter .e.g.
VMINLINE VM_BytecodeAction
ldc(REGISTER_ARGS_LIST, UDATA parmSize)
{
...
switch (romCPEntry->cpType & J9DescriptionCpTypeMask) {
...
case J9DescriptionCpTypeMethodHandle:
resolveMethodHandleRef(_currentThread, ramConstantPool, index,
-----> J9_RESOLVE_FLAG_RUNTIME_RESOLVE | J9_RESOLVE_FLAG_NO_CLASS_INIT);
break;
This should also require initializing the class at the point of method invoke or static field access, as we're no longer doing at MH resolution time.
This should also require initializing the class at the point of method invoke or static field access, as we're no longer doing at MH resolution time.
The MHInterpreter already does that, as do the thunkArchetype jit templates. I don't expect any changes required there but we should do some tests to confirm.
With the fix at Java_java_lang_invoke_MethodHandle_getCPMethodHandleAt
:
Java_java_lang_invoke_MethodHandle_getCPMethodHandleAt(JNIEnv *env, jclass unusedClass, jobject constantPoolOop, jint cpIndex)
{
...
if (NULL == methodHandleObject) {
methodHandleObject = vmFunctions->resolveMethodHandleRef(vmThread, J9_CP_FROM_CLASS(ramClass), cpIndex,
----> J9_RESOLVE_FLAG_RUNTIME_RESOLVE | J9_RESOLVE_FLAG_NO_CLASS_INIT);
}
The test results indicate it works good as expected:
$ ../jdk11_openj9_mh_debug_v4/bin/java Test
Class2.<clinit> called
obtaining method reference to Class1.dummyMethod
incrementing Class2.a
running Class1.dummyMethod via method reference
Class1.<clinit>: Class2.a is 1.0
Class1.dummyMethod: Class2.a is 1.0
$../jdk11_openj9_mh_debug_v4/bin/java -Xmx1024M -Xms1024M -jar server.jar
[11:31:07] [main/INFO]: Environment: authHost='https://authserver.mojang.com', accountsHost='https://api.mojang.com', sessionHost='https://sessionserver.mojang.com', name='PROD'
[11:31:09] [main/WARN]: Ambiguity between arguments [teleport, destination] and [teleport, targets] with inputs: [Player, 0123, @e, dd12be42-52a9-4a91-a8a1-11c01849e498]
[11:31:09] [main/WARN]: Ambiguity between arguments [teleport, location] and [teleport, destination] with inputs: [0.1 -0.5 .9, 0 0 0]
[11:31:09] [main/WARN]: Ambiguity between arguments [teleport, location] and [teleport, targets] with inputs: [0.1 -0.5 .9, 0 0 0]
[11:31:09] [main/WARN]: Ambiguity between arguments [teleport, targets] and [teleport, destination] with inputs: [Player, 0123, dd12be42-52a9-4a91-a8a1-11c01849e498]
[11:31:09] [main/WARN]: Ambiguity between arguments [teleport, targets, location] and [teleport, targets, destination] with inputs: [0.1 -0.5 .9, 0 0 0]
[11:31:09] [main/INFO]: Reloading ResourceManager: Default
[11:31:12] [Worker-Main-5/INFO]: Loaded 7 recipes
[11:31:13] [Worker-Main-5/INFO]: Loaded 927 advancements
[11:31:17] [Server thread/INFO]: Starting minecraft server version 1.16.2 Pre-release 1
[11:31:17] [Server thread/INFO]: Loading properties
[11:31:17] [Server thread/INFO]: Default game type: SURVIVAL
[11:31:17] [Server thread/INFO]: Generating keypair
[11:31:19] [Server thread/INFO]: Starting Minecraft server on *:25565
[11:31:19] [Server thread/INFO]: Using epoll channel type
[11:31:19] [Server thread/INFO]: Preparing level "world"
[11:31:20] [Server thread/WARN]: Unable to find spawn biome
[11:31:30] [Server thread/INFO]: Preparing start region for dimension minecraft:overworld
[11:31:30] [Worker-Main-4/INFO]: Preparing spawn area: 2%
[11:31:30] [Worker-Main-4/INFO]: Preparing spawn area: 2%
[11:31:31] [Worker-Main-4/INFO]: Preparing spawn area: 2%
[11:31:31] [Worker-Main-5/INFO]: Preparing spawn area: 2%
[11:31:32] [Worker-Main-5/INFO]: Preparing spawn area: 2%
[11:31:32] [Worker-Main-6/INFO]: Preparing spawn area: 3%
[11:31:33] [Worker-Main-6/INFO]: Preparing spawn area: 5%
[11:31:33] [Worker-Main-5/INFO]: Preparing spawn area: 9%
[11:31:34] [Worker-Main-5/INFO]: Preparing spawn area: 9%
[11:31:34] [Worker-Main-6/INFO]: Preparing spawn area: 10%
...
[11:31:56] [Worker-Main-4/INFO]: Preparing spawn area: 89%
[11:31:56] [Server thread/INFO]: Time elapsed: 26317 ms
[11:31:56] [Server thread/INFO]: Done (36.814s)! For help, type "help"
$ ../jdk11_openj9_mh_debug_v4/bin/java -Xmx1024M -Xms1024M -jar server.jar
[11:34:30] [main/INFO]: Environment: authHost='https://authserver.mojang.com', accountsHost='https://api.mojang.com', sessionHost='https://sessionserver.mojang.com', name='PROD'
[11:34:31] [main/WARN]: Ambiguity between arguments [teleport, destination] and [teleport, targets] with inputs: [Player, 0123, @e, dd12be42-52a9-4a91-a8a1-11c01849e498]
[11:34:31] [main/WARN]: Ambiguity between arguments [teleport, location] and [teleport, destination] with inputs: [0.1 -0.5 .9, 0 0 0]
[11:34:31] [main/WARN]: Ambiguity between arguments [teleport, location] and [teleport, targets] with inputs: [0.1 -0.5 .9, 0 0 0]
[11:34:31] [main/WARN]: Ambiguity between arguments [teleport, targets] and [teleport, destination] with inputs: [Player, 0123, dd12be42-52a9-4a91-a8a1-11c01849e498]
[11:34:31] [main/WARN]: Ambiguity between arguments [teleport, targets, location] and [teleport, targets, destination] with inputs: [0.1 -0.5 .9, 0 0 0]
[11:34:31] [main/INFO]: Reloading ResourceManager: Default
[11:34:32] [Worker-Main-5/INFO]: Loaded 7 recipes
[11:34:33] [Worker-Main-5/INFO]: Loaded 927 advancements
[11:34:37] [Server thread/INFO]: Starting minecraft server version 1.16.2 Pre-release 2
[11:34:37] [Server thread/INFO]: Loading properties
[11:34:37] [Server thread/INFO]: Default game type: SURVIVAL
[11:34:37] [Server thread/INFO]: Generating keypair
[11:34:37] [Server thread/INFO]: Starting Minecraft server on *:25565
[11:34:37] [Server thread/INFO]: Using epoll channel type
[11:34:37] [Server thread/INFO]: Preparing level "world"
[11:34:38] [Server thread/INFO]: Preparing start region for dimension minecraft:overworld
[11:34:45] [Server thread/INFO]: Preparing spawn area: 0%
[11:34:45] [Server thread/INFO]: Preparing spawn area: 0%
[11:34:45] [Server thread/INFO]: Preparing spawn area: 0%
...
[11:35:03] [Server thread/INFO]: Preparing spawn area: 91%
[11:35:04] [Worker-Main-4/INFO]: Preparing spawn area: 95%
[11:35:04] [Server thread/INFO]: Time elapsed: 26449 ms
[11:35:04] [Server thread/INFO]: Done (26.648s)! For help, type "help"
Will launch personal builds on Java 8 & 11 to ensure there is no issue introduced with the fix.
I verified two cases with simple tests with the fix above as @gacholio mentioned, which also works good as expected.
[1] initializing the class at the point of static field access:
public class Test2 {
public static class Class1 {
public static int a = 0;
static {
a = 1;
System.out.println("Class1.<clinit>: Class1.a = " + Class1.a);
}
}
public static void main(String[] args) {
int b = Class1.a;
System.out.println("b = " + b);
}
}
with the following output:
$ ../jdk11_openj9_mh_debug_v4/bin/java Test2
Class1.<clinit>: Class1.a = 1 <--- <clinit> was triggered before accessing the static filed
b = 1
[2] initializing the class at the point of method invoke
public class Test3 {
public static class Class1 {
public static void dummyMethod() {
System.out.println("calling Class1.dummyMethod");
}
static {
System.out.println("calling Class1.<clinit>");
}
}
public static void main(String[] args) {
Class1.dummyMethod();
}
}
with the following output:
$ ../jdk11_openj9_mh_debug_v4/bin/java Test3
calling Class1.<clinit> <------------ <clinit> was triggered before calling the static method
calling Class1.dummyMethod
Neither of those above tests are using MethodHandle, so I'm not sure what they prove.
[1] Test2 used MethodHandle as follows:
$ ../jdk11_openj9_mh_debug_v4/bin/javap -v -p Test2.class
Classfile /home/jincheng/X86_64_OPENJ9/issues_2020/10356_minecraft_load/Testcase/Test2.class
Last modified Aug. 18, 2020; size 921 bytes
MD5 checksum f63ac48827feb8bb8c2acefb99987c57
Compiled from "Test2.java"
...
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: getstatic #2 // Field Test2$Class1.a:I
3: istore_1
4: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
7: iload_1
----> 8: invokedynamic #4, 0 // InvokeDynamic #0:makeConcatWithConstants:(I)Ljava/lang/String;
13: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
16: return
LineNumberTable:
line 12: 0
line 13: 4
line 14: 16
}
SourceFile: "Test2.java"
NestMembers:
Test2$Class1
InnerClasses:
public static #9= #8 of #6; // Class1=class Test2$Class1 of class Test2
public static final #49= #48 of #51; // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
0: #25 REF_invokeStatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
Method arguments:
#26 b = \u0001
Test3 might need to rewritten.
{
public Test3();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=0, locals=1, args_size=1
0: invokestatic #2 // Method Test3$Class1.dummyMethod:()V
3: return
LineNumberTable:
line 13: 0
line 14: 3
}
SourceFile: "Test3.java"
NestMembers:
Test3$Class1
InnerClasses:
public static #6= #5 of #3; // Class1=class Test3$Class1 of class Test3
The MH use in Test2 appears to be from the string concatenation, not from the field access.
I modified Test2 to use MethodHandle as follows:
BaseExample.java
public class BaseExample {
public static int intField = 0;
static {
intField = 1;
System.out.println("calling BaseExample.<clinit>");
}
}
Test2.java
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodType;
public class Test2 {
public static void main(String[] args) throws Throwable {
MethodHandle intHandle = MethodHandles.lookup().findStaticGetter(BaseExample.class, "intField",int.class);
System.out.println("prepared to invoke intHandle");
int intValue = (int) intHandle.invoke();
System.out.println("intValue = " + intValue);
}
}
with the expected result:
$ ../jdk11_openj9_mh_debug_v4/bin/java Test2
prepared to invoke intHandle
calling BaseExample.<clinit> <------------------
intValue = 1
Updated Test3.java (for method) as follows:
BaseExample3.java
public class BaseExample3 {
public static int addPublicStatic (int a,int b) {return a + b;}
static {
System.out.println("calling BaseExample3.<clinit>");
}
}
Test3.java
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodType;
public class Test3 {
public static void main(String[] args) throws Throwable {
MethodHandle addHandle = MethodHandles.lookup().findStatic( BaseExample3.class, "addPublicStatic", MethodType.methodType( int.class, int.class, int.class ) );
System.out.println("prepared to invoke addHandle");
int sum = ( int )addHandle.invoke( 1, 2 );
System.out.println("sum = " + sum);
}
}
with the expected result:
$ ../jdk11_openj9_mh_debug_v4/bin/java Test3
prepared to invoke addHandle
calling BaseExample3.<clinit> <--------
sum = 3
Java -version output
Summary of problem
Minecraft 1.16.2-rc2 or Minecraft 1.16.2 client can not load on openj9. These version can load on hotspot.
https://bugs.mojang.com/browse/MC-197271 (server)
Diagnostic files
Only instead prints a stack trace.