8369150: NMethodRelocationTest fails when JVMTI events not published before JVM exit

Reviewed-by: lmesnik, sspitsyn
This commit is contained in:
Chad Rakoczy 2026-01-08 01:14:01 +00:00 committed by Leonid Mesnik
parent 9a944e5587
commit 0a1fa21921
4 changed files with 121 additions and 132 deletions

View File

@ -77,8 +77,6 @@ compiler/interpreter/Test6833129.java 8335266 generic-i586
compiler/c2/aarch64/TestStaticCallStub.java 8359963 linux-aarch64,macosx-aarch64
serviceability/jvmti/NMethodRelocation/NMethodRelocationTest.java 8369150 generic-all
#############################################################################
# :hotspot_gc

View File

@ -23,23 +23,30 @@
/*
* @test
*
* @bug 8316694
* @summary Verify that nmethod relocation posts the correct JVMTI events
* @requires vm.jvmti
* @requires vm.gc == "null" | vm.gc == "Serial"
* @requires vm.jvmti &
* vm.gc != "Epsilon" &
* vm.flavor == "server" &
* !vm.emulatedClient &
* (vm.opt.TieredStopAtLevel == null | vm.opt.TieredStopAtLevel == 4)
* @library /test/lib /test/hotspot/jtreg
* @build jdk.test.whitebox.WhiteBox
* @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
* @run main/othervm/native NMethodRelocationTest
* @run main/othervm/native -agentlib:NMethodRelocationTest
* --enable-native-access=ALL-UNNAMED
* -Xbootclasspath/a:.
* -Xbatch
* -XX:+UnlockDiagnosticVMOptions
* -XX:+WhiteBoxAPI
* -XX:+SegmentedCodeCache
* -XX:-TieredCompilation
* -XX:+UnlockExperimentalVMOptions
* -XX:+NMethodRelocation
* NMethodRelocationTest
*/
import static compiler.whitebox.CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION;
import java.lang.reflect.Executable;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import jdk.test.lib.Asserts;
import jdk.test.lib.process.OutputAnalyzer;
@ -48,52 +55,9 @@ import jdk.test.whitebox.WhiteBox;
import jdk.test.whitebox.code.BlobType;
import jdk.test.whitebox.code.NMethod;
import static compiler.whitebox.CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION;
public class NMethodRelocationTest {
public static void main(String[] args) throws Exception {
ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder(
"-agentlib:NMethodRelocationTest",
"--enable-native-access=ALL-UNNAMED",
"-Xbootclasspath/a:.",
"-XX:+UseSerialGC",
"-XX:+UnlockDiagnosticVMOptions",
"-XX:+WhiteBoxAPI",
"-XX:+SegmentedCodeCache",
"-XX:-TieredCompilation",
"-XX:+UnlockExperimentalVMOptions",
"-XX:+NMethodRelocation",
"DoWork");
OutputAnalyzer oa = new OutputAnalyzer(pb.start());
String output = oa.getOutput();
if (oa.getExitValue() != 0) {
System.err.println(oa.getOutput());
throw new RuntimeException("Non-zero exit code returned from the test");
}
Asserts.assertTrue(oa.getExitValue() == 0);
Pattern pattern = Pattern.compile("(?m)^Relocated nmethod from (0x[0-9a-f]{16}) to (0x[0-9a-f]{16})$");
Matcher matcher = pattern.matcher(output);
if (matcher.find()) {
String fromAddr = matcher.group(1);
String toAddr = matcher.group(2);
// Confirm events sent for both original and relocated nmethod
oa.shouldContain("<COMPILED_METHOD_LOAD>: name: compiledMethod, code: " + fromAddr);
oa.shouldContain("<COMPILED_METHOD_LOAD>: name: compiledMethod, code: " + toAddr);
oa.shouldContain("<COMPILED_METHOD_UNLOAD>: name: compiledMethod, code: " + fromAddr);
oa.shouldContain("<COMPILED_METHOD_UNLOAD>: name: compiledMethod, code: " + toAddr);
} else {
System.err.println(oa.getOutput());
throw new RuntimeException("Unable to find relocation information");
}
}
}
class DoWork {
protected static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox();
/** Load native library if required. */
static {
@ -107,43 +71,18 @@ class DoWork {
}
}
/**
* Returns value of VM option.
*
* @param name option's name
* @return value of option or {@code null}, if option doesn't exist
* @throws NullPointerException if name is null
*/
protected static String getVMOption(String name) {
Objects.requireNonNull(name);
return Objects.toString(WHITE_BOX.getVMFlag(name), null);
}
protected static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox();
/**
* Returns value of VM option or default value.
*
* @param name option's name
* @param defaultValue default value
* @return value of option or {@code defaultValue}, if option doesn't exist
* @throws NullPointerException if name is null
* @see #getVMOption(String)
*/
protected static String getVMOption(String name, String defaultValue) {
String result = getVMOption(name);
return result == null ? defaultValue : result;
}
native static boolean shouldExit();
public static void main(String argv[]) throws Exception {
run();
}
public static void run() throws Exception {
Executable method = DoWork.class.getDeclaredMethod("compiledMethod");
public static void main(String[] argv) throws Exception {
Executable method = NMethodRelocationTest.class.getDeclaredMethod("compiledMethod");
WHITE_BOX.testSetDontInlineMethod(method, true);
WHITE_BOX.enqueueMethodForCompilation(method, COMP_LEVEL_FULL_OPTIMIZATION);
while (WHITE_BOX.isMethodQueuedForCompilation(method)) {
Thread.onSpinWait();
if (!WHITE_BOX.isMethodCompiled(method)) {
throw new AssertionError("Method not compiled");
}
NMethod originalNMethod = NMethod.get(method, false);
@ -164,13 +103,9 @@ class DoWork {
WHITE_BOX.deoptimizeAll();
WHITE_BOX.fullGC();
WHITE_BOX.fullGC();
WHITE_BOX.lockCompilation();
System.out.printf("Relocated nmethod from 0x%016x to 0x%016x%n", originalNMethod.code_begin, relocatedNMethod.code_begin);
System.out.flush();
while (!shouldExit()) {
WHITE_BOX.fullGC();
}
}
public static long compiledMethod() {

View File

@ -21,10 +21,20 @@
* questions.
*/
#include <atomic>
#include <inttypes.h>
#include <jvmti.h>
#include <stdio.h>
#include <string.h>
#include "jvmti_common.hpp"
extern "C" {
// Track nmethod addresses for LOAD and UNLOAD events
static const void* first_load_addr = nullptr;
static const void* second_load_addr = nullptr;
static const void* first_unload_addr = nullptr;
static const void* second_unload_addr = nullptr;
// Keep track of test completion
static std::atomic<bool> should_exit{false};
/**
* Callback for COMPILED_METHOD_LOAD event.
@ -34,18 +44,27 @@ callbackCompiledMethodLoad(jvmtiEnv* jvmti, jmethodID method,
jint code_size, const void* code_addr,
jint map_length, const jvmtiAddrLocationMap* map,
const void* compile_info) {
char* name = nullptr;
char* sig = nullptr;
if (jvmti->GetMethodName(method, &name, &sig, nullptr) != JVMTI_ERROR_NONE) {
printf(" [Could not retrieve method name]\n");
fflush(stdout);
// Only track events for "compiledMethod"
char* name = get_method_name(jvmti, method);
if (strcmp(name, "compiledMethod") != 0) {
return;
}
printf("<COMPILED_METHOD_LOAD>: name: %s, code: 0x%016" PRIxPTR "\n",
name, (uintptr_t)code_addr);
fflush(stdout);
LOG("<COMPILED_METHOD_LOAD>: name: %s, code: 0x%016" PRIxPTR "\n", name, (uintptr_t)code_addr);
if (first_load_addr == nullptr) {
first_load_addr = code_addr;
} else if (second_load_addr == nullptr) {
second_load_addr = code_addr;
// Verify that the addresses are different
if (first_load_addr == second_load_addr) {
fatal("Load events for 'compiledMethod' are expected to use different addresses");
}
} else {
fatal("Received too many load events for 'compiledMethod'");
}
}
/**
@ -54,25 +73,50 @@ callbackCompiledMethodLoad(jvmtiEnv* jvmti, jmethodID method,
JNIEXPORT void JNICALL
callbackCompiledMethodUnload(jvmtiEnv* jvmti, jmethodID method,
const void* code_addr) {
char* name = nullptr;
char* sig = nullptr;
if (jvmti->GetMethodName(method, &name, &sig, nullptr) != JVMTI_ERROR_NONE) {
printf(" [Could not retrieve method name]\n");
fflush(stdout);
// Only track events for "compiledMethod"
char* name = get_method_name(jvmti, method);
if (strcmp(name, "compiledMethod") != 0) {
return;
}
printf("<COMPILED_METHOD_UNLOAD>: name: %s, code: 0x%016" PRIxPTR "\n",
name, (uintptr_t)code_addr);
fflush(stdout);
LOG("<COMPILED_METHOD_UNLOAD>: name: %s, code: 0x%016" PRIxPTR "\n", name, (uintptr_t)code_addr);
// Validate both loads have occurred
if (first_load_addr == nullptr || second_load_addr == nullptr) {
fatal("UNLOAD event for 'compiledMethod' occurred before both LOAD events");
}
if (first_unload_addr == nullptr) {
first_unload_addr = code_addr;
} else if (second_unload_addr == nullptr) {
second_unload_addr = code_addr;
// Verify that the addresses are different
if (first_unload_addr == second_unload_addr) {
fatal("Unload events for 'compiledMethod' are expected to use different addresses");
}
// LOAD and UNLOAD events should report the same two addresses, but the order of
// the UNLOADs is not guaranteed, since the GC may unload either nmethod first.
if ((first_load_addr == first_unload_addr && second_load_addr == second_unload_addr) ||
(first_load_addr == second_unload_addr && second_load_addr == first_unload_addr)) {
// Update should_exit to signal test completion
should_exit.store(true);
} else {
fatal("Address mismatch for 'compiledMethod' events");
}
} else {
fatal("Received too many unload events for 'compiledMethod'");
}
}
JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) {
jvmtiEnv* jvmti = nullptr;
jvmtiError error;
if (jvm->GetEnv((void **)&jvmti, JVMTI_VERSION_1_0) != JNI_OK) {
printf("Unable to access JVMTI!\n");
LOG("Unable to access JVMTI!\n");
return JNI_ERR;
}
@ -80,11 +124,8 @@ JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *jvm, char *options, void *reserved)
jvmtiCapabilities caps;
memset(&caps, 0, sizeof(caps));
caps.can_generate_compiled_method_load_events = 1;
error = jvmti->AddCapabilities(&caps);
if (error != JVMTI_ERROR_NONE) {
printf("ERROR: Unable to add capabilities, error=%d\n", error);
return JNI_ERR;
}
jvmtiError error = jvmti->AddCapabilities(&caps);
check_jvmti_error(error, "Unable to add capabilities");
// Set event callbacks
jvmtiEventCallbacks eventCallbacks;
@ -92,23 +133,21 @@ JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *jvm, char *options, void *reserved)
eventCallbacks.CompiledMethodLoad = callbackCompiledMethodLoad;
eventCallbacks.CompiledMethodUnload = callbackCompiledMethodUnload;
error = jvmti->SetEventCallbacks(&eventCallbacks, sizeof(eventCallbacks));
if (error != JVMTI_ERROR_NONE) {
printf("ERROR: Unable to set event callbacks, error=%d\n", error);
return JNI_ERR;
}
check_jvmti_error(error, "Unable to set event callbacks");
// Enable events
error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_COMPILED_METHOD_LOAD, nullptr);
if (error != JVMTI_ERROR_NONE) {
printf("ERROR: Unable to enable COMPILED_METHOD_LOAD event, error=%d\n", error);
return JNI_ERR;
}
check_jvmti_error(error, "Unable to enable COMPILED_METHOD_LOAD event");
error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_COMPILED_METHOD_UNLOAD, nullptr);
if (error != JVMTI_ERROR_NONE) {
printf("ERROR: Unable to enable COMPILED_METHOD_UNLOAD event, error=%d\n", error);
return JNI_ERR;
}
check_jvmti_error(error, "Unable to enable COMPILED_METHOD_UNLOAD event");
return JNI_OK;
}
JNIEXPORT jboolean JNICALL
Java_NMethodRelocationTest_shouldExit(JNIEnv *env, jclass cls) {
return should_exit.load();
}
}

View File

@ -123,6 +123,12 @@ char* julong_to_string(julong value, char *string) {
return string;
}
static void
fatal(const char* msg) {
LOG("FATAL ERROR: %s\n", msg);
abort();
}
static void
fatal(JNIEnv* jni, const char* msg) {
jni->FatalError(msg);
@ -313,6 +319,17 @@ get_thread_name(jvmtiEnv *jvmti, JNIEnv* jni, jthread thread) {
return tname;
}
static char*
get_method_name(jvmtiEnv *jvmti, jmethodID method) {
char* mname = nullptr;
jvmtiError err;
err = jvmti->GetMethodName(method, &mname, nullptr, nullptr);
check_jvmti_error(err, "get_method_name: error in JVMTI GetMethodName call");
return mname;
}
static char*
get_method_name(jvmtiEnv *jvmti, JNIEnv* jni, jmethodID method) {
char* mname = nullptr;