ADDITIONAL SYSTEM INFORMATION :
Docker image used to reproduce the issue is 's390x/eclipse-temurin:24'.
A DESCRIPTION OF THE PROBLEM :
On Linux s390x, running a Java app that uses virtual threads with a JVMTI agent that uses the `VirtualThreadEnd` event callback with the `-Xcheck:jni` option, reports the following FATAL error when using `IsSameObject` with the thread object passed as virtual thread in `VirtualThreadEnd`:
```
+ ./run.sh
starting virtual thread
virtualThreadStart
joining virtual thread
IsVirtualThread(0x3fee8000f00): 1
IsSameObject(0x3fee8000f00, nullptr): 0
hello from the virtual thread
virtualThreadEnd
IsVirtualThread(0x3ff984b2b78): 1
FATAL ERROR in native method: Bad global or local ref passed to JNI
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
V [libjvm.so+0x77b8cc] ReportJNIFatalError+0x44
V [libjvm.so+0x78d97e] checked_jni_IsSameObject+0x216
./run.sh: line 3: 10 Aborted (core dumped) java --enable-preview -Xcheck:jni -agentpath:agent/agent.so VirtualThreadsSample.java
```
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Compile the provided JVMTI agent code and run the provided Java code with
```
java -Xcheck:jni -agentpath:agent/agent.so VirtualThreadsSample.java
```
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Program runs successfully without fatal error reported.
ACTUAL -
Program aborts with the following fatal error:
```
FATAL ERROR in native method: Bad global or local ref passed to JNI
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
V [libjvm.so+0x77b8cc] ReportJNIFatalError+0x44
V [libjvm.so+0x78d97e] checked_jni_IsSameObject+0x216
```
---------- BEGIN SOURCE ----------
JVMTI agent code (agent.cpp, C++17)
```
#include <jvmti.h>
#include <cstdio>
#include <string>
namespace {
void printJvmtiError(jvmtiEnv* const jvmti, jvmtiError const error, std::string const& msg) {
char* errorName = nullptr;
jvmti->GetErrorName(error, &errorName);
fprintf(stderr, "error: jvmti: %s: %s (%d)\n", msg.c_str(), (errorName != nullptr ? errorName : "<unknown>"), error);
}
void JNICALL threadStart(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread) {
fprintf(stderr, "threadStart\n");
}
void JNICALL threadEnd(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread) {
fprintf(stderr, "threadEnd\n");
}
void JNICALL virtualThreadStart(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread) {
fprintf(stderr, "virtualThreadStart\n");
fprintf(stderr, "IsVirtualThread(%p): %d\n", thread, jni->IsVirtualThread(thread));
fprintf(stderr, "IsSameObject(%p, nullptr): %d\n", thread, jni->IsSameObject(thread, nullptr));
}
void JNICALL virtualThreadEnd(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread) {
fprintf(stderr, "virtualThreadEnd\n");
fprintf(stderr, "IsVirtualThread(%p): %d\n", thread, jni->IsVirtualThread(thread));
fprintf(stderr, "IsSameObject(%p, nullptr): %d\n", thread, jni->IsSameObject(thread, nullptr));
}
} // anonymous namespace
JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* jvm, char* /*options*/, void* /*reserved*/) {
jvmtiEnv* jvmti = nullptr;
if (auto const error = jvm->GetEnv(reinterpret_cast<void**>(&jvmti), JVMTI_VERSION_1_0); error != JNI_OK || jvmti == nullptr) {
fprintf(stderr, "failed to get access to JVMTI version 1.0\n");
return JNI_ERR;
}
jvmtiCapabilities capabilities{};
capabilities.can_support_virtual_threads = 1;
if (auto const error = jvmti->AddCapabilities(&capabilities); error != JVMTI_ERROR_NONE) {
printJvmtiError(jvmti, error, "failed to add the required capabilities");
return JNI_ERR;
}
if (auto const error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_THREAD_START, nullptr); error != JVMTI_ERROR_NONE) {
printJvmtiError(jvmti, error, "failed to enable thread start events");
return JNI_ERR;
}
if (auto const error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_THREAD_END, nullptr); error != JVMTI_ERROR_NONE) {
printJvmtiError(jvmti, error, "failed to enable thread end events");
return JNI_ERR;
}
if (auto const error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VIRTUAL_THREAD_START, nullptr); error != JVMTI_ERROR_NONE) {
printJvmtiError(jvmti, error, "failed to enable virtual thread start events");
return JNI_ERR;
}
if (auto const error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VIRTUAL_THREAD_END, nullptr); error != JVMTI_ERROR_NONE) {
printJvmtiError(jvmti, error, "failed to enable virtual thread end events");
return JNI_ERR;
}
jvmtiEventCallbacks eventCallbacks{};
// eventCallbacks.ThreadStart = threadStart;
// eventCallbacks.ThreadEnd = threadEnd;
eventCallbacks.VirtualThreadStart = virtualThreadStart;
eventCallbacks.VirtualThreadEnd = virtualThreadEnd;
if (auto const error = jvmti->SetEventCallbacks(&eventCallbacks, sizeof(eventCallbacks)); error != JVMTI_ERROR_NONE) {
printJvmtiError(jvmti, error, "failed to set event callbacks");
return JNI_ERR;
}
return JNI_OK;
}
```
Java sample code (VirtualThreadsSample.java)
```
import java.time.Duration;
public class VirtualThreadsSample {
public static void main(final String[] args) throws InterruptedException {
System.out.println("starting virtual thread");
final Thread virtualThread = Thread.ofVirtual().start(() -> {
System.out.println("hello from the virtual thread");
try {
Thread.sleep(Duration.ofSeconds(1));
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println("joining virtual thread");
virtualThread.join(Duration.ofSeconds(10));
System.out.println("finished sample");
}
}
```
---------- END SOURCE ----------
Docker image used to reproduce the issue is 's390x/eclipse-temurin:24'.
A DESCRIPTION OF THE PROBLEM :
On Linux s390x, running a Java app that uses virtual threads with a JVMTI agent that uses the `VirtualThreadEnd` event callback with the `-Xcheck:jni` option, reports the following FATAL error when using `IsSameObject` with the thread object passed as virtual thread in `VirtualThreadEnd`:
```
+ ./run.sh
starting virtual thread
virtualThreadStart
joining virtual thread
IsVirtualThread(0x3fee8000f00): 1
IsSameObject(0x3fee8000f00, nullptr): 0
hello from the virtual thread
virtualThreadEnd
IsVirtualThread(0x3ff984b2b78): 1
FATAL ERROR in native method: Bad global or local ref passed to JNI
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
V [libjvm.so+0x77b8cc] ReportJNIFatalError+0x44
V [libjvm.so+0x78d97e] checked_jni_IsSameObject+0x216
./run.sh: line 3: 10 Aborted (core dumped) java --enable-preview -Xcheck:jni -agentpath:agent/agent.so VirtualThreadsSample.java
```
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Compile the provided JVMTI agent code and run the provided Java code with
```
java -Xcheck:jni -agentpath:agent/agent.so VirtualThreadsSample.java
```
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Program runs successfully without fatal error reported.
ACTUAL -
Program aborts with the following fatal error:
```
FATAL ERROR in native method: Bad global or local ref passed to JNI
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
V [libjvm.so+0x77b8cc] ReportJNIFatalError+0x44
V [libjvm.so+0x78d97e] checked_jni_IsSameObject+0x216
```
---------- BEGIN SOURCE ----------
JVMTI agent code (agent.cpp, C++17)
```
#include <jvmti.h>
#include <cstdio>
#include <string>
namespace {
void printJvmtiError(jvmtiEnv* const jvmti, jvmtiError const error, std::string const& msg) {
char* errorName = nullptr;
jvmti->GetErrorName(error, &errorName);
fprintf(stderr, "error: jvmti: %s: %s (%d)\n", msg.c_str(), (errorName != nullptr ? errorName : "<unknown>"), error);
}
void JNICALL threadStart(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread) {
fprintf(stderr, "threadStart\n");
}
void JNICALL threadEnd(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread) {
fprintf(stderr, "threadEnd\n");
}
void JNICALL virtualThreadStart(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread) {
fprintf(stderr, "virtualThreadStart\n");
fprintf(stderr, "IsVirtualThread(%p): %d\n", thread, jni->IsVirtualThread(thread));
fprintf(stderr, "IsSameObject(%p, nullptr): %d\n", thread, jni->IsSameObject(thread, nullptr));
}
void JNICALL virtualThreadEnd(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread) {
fprintf(stderr, "virtualThreadEnd\n");
fprintf(stderr, "IsVirtualThread(%p): %d\n", thread, jni->IsVirtualThread(thread));
fprintf(stderr, "IsSameObject(%p, nullptr): %d\n", thread, jni->IsSameObject(thread, nullptr));
}
} // anonymous namespace
JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* jvm, char* /*options*/, void* /*reserved*/) {
jvmtiEnv* jvmti = nullptr;
if (auto const error = jvm->GetEnv(reinterpret_cast<void**>(&jvmti), JVMTI_VERSION_1_0); error != JNI_OK || jvmti == nullptr) {
fprintf(stderr, "failed to get access to JVMTI version 1.0\n");
return JNI_ERR;
}
jvmtiCapabilities capabilities{};
capabilities.can_support_virtual_threads = 1;
if (auto const error = jvmti->AddCapabilities(&capabilities); error != JVMTI_ERROR_NONE) {
printJvmtiError(jvmti, error, "failed to add the required capabilities");
return JNI_ERR;
}
if (auto const error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_THREAD_START, nullptr); error != JVMTI_ERROR_NONE) {
printJvmtiError(jvmti, error, "failed to enable thread start events");
return JNI_ERR;
}
if (auto const error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_THREAD_END, nullptr); error != JVMTI_ERROR_NONE) {
printJvmtiError(jvmti, error, "failed to enable thread end events");
return JNI_ERR;
}
if (auto const error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VIRTUAL_THREAD_START, nullptr); error != JVMTI_ERROR_NONE) {
printJvmtiError(jvmti, error, "failed to enable virtual thread start events");
return JNI_ERR;
}
if (auto const error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VIRTUAL_THREAD_END, nullptr); error != JVMTI_ERROR_NONE) {
printJvmtiError(jvmti, error, "failed to enable virtual thread end events");
return JNI_ERR;
}
jvmtiEventCallbacks eventCallbacks{};
// eventCallbacks.ThreadStart = threadStart;
// eventCallbacks.ThreadEnd = threadEnd;
eventCallbacks.VirtualThreadStart = virtualThreadStart;
eventCallbacks.VirtualThreadEnd = virtualThreadEnd;
if (auto const error = jvmti->SetEventCallbacks(&eventCallbacks, sizeof(eventCallbacks)); error != JVMTI_ERROR_NONE) {
printJvmtiError(jvmti, error, "failed to set event callbacks");
return JNI_ERR;
}
return JNI_OK;
}
```
Java sample code (VirtualThreadsSample.java)
```
import java.time.Duration;
public class VirtualThreadsSample {
public static void main(final String[] args) throws InterruptedException {
System.out.println("starting virtual thread");
final Thread virtualThread = Thread.ofVirtual().start(() -> {
System.out.println("hello from the virtual thread");
try {
Thread.sleep(Duration.ofSeconds(1));
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println("joining virtual thread");
virtualThread.join(Duration.ofSeconds(10));
System.out.println("finished sample");
}
}
```
---------- END SOURCE ----------