ADDITIONAL SYSTEM INFORMATION :
Ubunu 22.10 x86_64
openjdk version "20-ea" 2023-03-21
OpenJDK Runtime Environment (build 20-ea+29-2280)
OpenJDK 64-Bit Server VM (build 20-ea+29-2280, mixed mode, sharing)
A DESCRIPTION OF THE PROBLEM :
Objects only referenced by the stack of unmounted VirtualThreads are not visit in any way. Maybe this could be done as a new kind of instance reference of StackChunk or as a new synthetic root. This functionality would be necessary for a JVMTI-based heap dump.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
With the attached sources and JAVA_HOME set to a JDK 20:
g++ -shared -I$JAVA_HOME/include -I$JAVA_HOME/include/linux -fPIC VThreadStackRefTest.cpp -o libVThreadStackRefTest.so
$JAVA_HOME/bin/javac --enable-preview --release=20 VThreadStackRefTest.java
LD_LIBRARY_PATH=. $JAVA_HOME/bin/java --enable-preview -agentlib:VThreadStackRefTest VThreadStackRefTest
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The test should exit with 0.
ACTUAL -
The test shows "did not find expected references: VThreadReferenced: 0, PThreadReferenced: 1" and exits with 1.
---------- BEGIN SOURCE ----------
-- VThreadStackRefTest.java -----------------------------------------------------------
import java.util.concurrent.CountDownLatch;
public class VThreadStackRefTest {
private static native int[] getReferenceCount(Class<?>... classes);
public static void main(String[] args) throws InterruptedException {
CountDownLatch dumpedLatch = new CountDownLatch(1);
Thread vthread = Thread.ofVirtual().start(() -> {
Object referenced = new VThreadReferenced();
System.out.println(referenced.getClass());
await(dumpedLatch);
System.out.println(referenced.getClass());
});
Thread pthread = Thread.ofPlatform().start(() -> {
Object referenced = new PThreadReferenced();
System.out.println(referenced.getClass());
await(dumpedLatch);
System.out.println(referenced.getClass());
});
Thread.sleep(2000); // wait for reference and unmount
int[] count = getReferenceCount(VThreadReferenced.class, PThreadReferenced.class);
dumpedLatch.countDown();
vthread.join();
pthread.join();
if (count[0] != 1 || count[1] != 1) {
System.err.println("did not find expected references: VThreadReferenced: " + count[0] + ", PThreadReferenced: " + count[1]);
System.exit(1);
}
}
private static void await(CountDownLatch dumpedLatch) {
try {
dumpedLatch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
public static class VThreadReferenced {
}
public static class PThreadReferenced {
}
}
--------------------------------------------------------------------------------------
-- VThreadStackRefTest.cpp -----------------------------------------------------------
#include <jvmti.h>
#include <cstdlib>
#include <cstring>
namespace {
jvmtiEnv *jvmti = nullptr;
void checkJvmti(int code, const char* message) {
if (code != JVMTI_ERROR_NONE) {
printf("Error %s: %d\n", message, code);
abort();
}
}
const int TAG_START = 100;
}
jint JNICALL testJvmtiHeapReferenceCallback(jvmtiHeapReferenceKind reference_kind, const jvmtiHeapReferenceInfo* reference_info,
jlong class_tag, jlong referrer_class_tag, jlong size, jlong* tag_ptr, jlong* referrer_tag_ptr, jint length, void* user_data) {
if (class_tag >= TAG_START) {
((jint*)user_data)[class_tag - TAG_START]++;
}
return JVMTI_VISIT_OBJECTS;
}
extern "C" JNIEXPORT jintArray JNICALL Java_VThreadStackRefTest_getReferenceCount(JNIEnv* env, jclass clazz, jobjectArray classes) {
int classesCount = env->GetArrayLength(classes);
for (int i=0; i<classesCount; i++) {
jvmti->SetTag(env->GetObjectArrayElement(classes, i), TAG_START + i);
}
jint* counter = new jint[classesCount];
jvmtiHeapCallbacks heapCallBacks;
memset(&heapCallBacks, 0, sizeof(jvmtiHeapCallbacks));
heapCallBacks.heap_reference_callback = testJvmtiHeapReferenceCallback;
checkJvmti(jvmti->FollowReferences(0, nullptr, nullptr, &heapCallBacks, counter), "follow references");
jintArray result = env->NewIntArray(classesCount);
env->SetIntArrayRegion(result, 0, classesCount, counter);
return result;
}
extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved) {
if (vm->GetEnv(reinterpret_cast<void **>(&jvmti), JVMTI_VERSION) != JNI_OK || !jvmti) {
printf("Could not initialize JVMTI\n");
abort();
}
jvmtiCapabilities capabilities;
memset(&capabilities, 0, sizeof(capabilities));
capabilities.can_tag_objects = 1;
checkJvmti(jvmti->AddCapabilities(&capabilities), "adding capabilities");
return JVMTI_ERROR_NONE;
}
--------------------------------------------------------------------------------------
---------- END SOURCE ----------
FREQUENCY : always