diff --git a/make/test/JtregNativeHotspot.gmk b/make/test/JtregNativeHotspot.gmk index 8916eee90d7..97f2f12cb76 100644 --- a/make/test/JtregNativeHotspot.gmk +++ b/make/test/JtregNativeHotspot.gmk @@ -885,7 +885,7 @@ BUILD_HOTSPOT_JTREG_EXECUTABLES_JDK_LIBS_exedaemonDestroy := java.base:libjvm ifeq ($(call isTargetOs, windows), true) BUILD_HOTSPOT_JTREG_EXECUTABLES_CFLAGS_exeFPRegs := -MT - BUILD_HOTSPOT_JTREG_EXCLUDE += exesigtest.c libterminatedThread.c libTestJNI.c libCompleteExit.c libMonitorWithDeadObjectTest.c libTestPsig.c exeGetCreatedJavaVMs.c + BUILD_HOTSPOT_JTREG_EXCLUDE += exesigtest.c libterminatedThread.c libTestJNI.c libCompleteExit.c libMonitorWithDeadObjectTest.c libTestPsig.c exeGetCreatedJavaVMs.c libTestUnloadedClass.cpp BUILD_HOTSPOT_JTREG_LIBRARIES_JDK_LIBS_libnativeStack := java.base:libjvm BUILD_HOTSPOT_JTREG_LIBRARIES_JDK_LIBS_libVThreadEventTest := java.base:libjvm else @@ -1526,6 +1526,7 @@ else BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libCompleteExit += -lpthread BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libMonitorWithDeadObjectTest += -lpthread BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libnativeStack += -lpthread + BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libTestUnloadedClass += -lpthread BUILD_HOTSPOT_JTREG_LIBRARIES_JDK_LIBS_libVThreadEventTest := java.base:libjvm BUILD_HOTSPOT_JTREG_EXECUTABLES_LIBS_exeGetCreatedJavaVMs := -lpthread BUILD_HOTSPOT_JTREG_EXECUTABLES_JDK_LIBS_exeGetCreatedJavaVMs := java.base:libjvm diff --git a/src/hotspot/share/prims/jvmtiEnv.cpp b/src/hotspot/share/prims/jvmtiEnv.cpp index a36d12059a9..097ce331540 100644 --- a/src/hotspot/share/prims/jvmtiEnv.cpp +++ b/src/hotspot/share/prims/jvmtiEnv.cpp @@ -3132,7 +3132,9 @@ JvmtiEnv::GetFieldName(fieldDescriptor* fdesc_ptr, char** name_ptr, char** signa // declaring_class_ptr - pre-checked for null jvmtiError JvmtiEnv::GetFieldDeclaringClass(fieldDescriptor* fdesc_ptr, jclass* declaring_class_ptr) { - + // As for the GetFieldDeclaringClass method, the XSL generated C++ code that calls it has + // a jclass of the relevant class or a subclass of it, which is fine in terms of ensuring + // the holder is kept alive. *declaring_class_ptr = get_jni_class_non_null(fdesc_ptr->field_holder()); return JVMTI_ERROR_NONE; } /* end GetFieldDeclaringClass */ @@ -3210,7 +3212,9 @@ JvmtiEnv::GetMethodName(Method* method, char** name_ptr, char** signature_ptr, c jvmtiError JvmtiEnv::GetMethodDeclaringClass(Method* method, jclass* declaring_class_ptr) { NULL_CHECK(method, JVMTI_ERROR_INVALID_METHODID); - (*declaring_class_ptr) = get_jni_class_non_null(method->method_holder()); + Klass* k = method->method_holder(); + Handle holder(Thread::current(), k->klass_holder()); // keep the klass alive + (*declaring_class_ptr) = get_jni_class_non_null(k); return JVMTI_ERROR_NONE; } /* end GetMethodDeclaringClass */ diff --git a/src/hotspot/share/prims/jvmtiEnvBase.cpp b/src/hotspot/share/prims/jvmtiEnvBase.cpp index a05d6998093..167ca557cde 100644 --- a/src/hotspot/share/prims/jvmtiEnvBase.cpp +++ b/src/hotspot/share/prims/jvmtiEnvBase.cpp @@ -599,6 +599,7 @@ JvmtiEnvBase::jvf_for_thread_and_depth(JavaThread* java_thread, jint depth) { jclass JvmtiEnvBase::get_jni_class_non_null(Klass* k) { assert(k != nullptr, "k != null"); + assert(k->is_loader_alive(), "Must be alive"); Thread *thread = Thread::current(); return (jclass)jni_reference(Handle(thread, k->java_mirror())); } diff --git a/test/hotspot/jtreg/serviceability/jvmti/GetMethodDeclaringClass/TestUnloadedClass.java b/test/hotspot/jtreg/serviceability/jvmti/GetMethodDeclaringClass/TestUnloadedClass.java new file mode 100644 index 00000000000..dafec97111e --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/GetMethodDeclaringClass/TestUnloadedClass.java @@ -0,0 +1,106 @@ +/* + * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. + * Copyright (c) 2024, Alibaba Group Holding Limited. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8339725 + * @summary Stress test GetMethodDeclaringClass + * @requires vm.jvmti + * @requires (os.family == "linux") + * @library /test/lib + * @run driver/timeout=300 TestUnloadedClass + */ + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.Utils; +import jdk.test.lib.process.ProcessTools; +import jdk.test.lib.Platform; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.InputStream; +import java.lang.reflect.Constructor; + +public class TestUnloadedClass { + public static void main(String[] args) throws Exception { + ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder( + "-agentpath:" + Utils.TEST_NATIVE_PATH + File.separator + System.mapLibraryName("TestUnloadedClass"), + "-Xmx50m", + "Test"); + OutputAnalyzer output = new OutputAnalyzer(pb.start()); + if (!Platform.isDebugBuild()) { + output.shouldContain("OutOfMemoryError"); + } + } +} + +class Test { + public static void main(String[] args) throws Exception { + long last = System.nanoTime(); + for (int i = 0;;i++) { + if (Platform.isDebugBuild() && i >= 1000) { + // Debug build costs too much time to OOM so limit the loop iteration + break; + } + CustomClassLoader loader = new CustomClassLoader(); + Class k = loader.findClass("MyClass"); + Constructor c = k.getDeclaredConstructor(); + c.setAccessible(true); + c.newInstance(); + + // call gc every ~1 second. + if ((System.nanoTime() - last) >= 1e9) { + System.gc(); + last = System.nanoTime(); + } + } + } +} + +class CustomClassLoader extends ClassLoader { + static byte[] BYTES; + + static { + try (InputStream in = CustomClassLoader.class.getResourceAsStream("MyClass.class")) { + try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + byte[] buf = new byte[4096]; + int len; + while ((len = in.read(buf)) != -1) { + baos.write(buf, 0, len); + } + BYTES = baos.toByteArray(); + } + } catch (Throwable t) { + throw new RuntimeException(t); + } + } + + @Override + public Class findClass(String name) throws ClassNotFoundException { + return defineClass(name, BYTES, 0, BYTES.length); + } +} + +class MyClass { +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/GetMethodDeclaringClass/libTestUnloadedClass.cpp b/test/hotspot/jtreg/serviceability/jvmti/GetMethodDeclaringClass/libTestUnloadedClass.cpp new file mode 100644 index 00000000000..c32b787d578 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/GetMethodDeclaringClass/libTestUnloadedClass.cpp @@ -0,0 +1,129 @@ +/* + * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. + * Copyright (c) 2024, Alibaba Group Holding Limited. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include + +#include +#include +#include +#include +#include +#include + +static jvmtiEnv *_jvmti; +static JavaVM *_jvm; + +#define BUFFER_SIZE 100000 +static std::atomic ring_buffer[BUFFER_SIZE]; + +void get_method_details(jmethodID method) { + jclass method_class; + char *class_name = NULL; + if (_jvmti->GetMethodDeclaringClass(method, &method_class) == JVMTI_ERROR_NONE) { + if (_jvmti->GetClassSignature(method_class, &class_name, NULL) == JVMTI_ERROR_NONE) { + _jvmti->Deallocate((unsigned char *)class_name); + } + } +} + +void* read_ringbuffer(void* arg) { + JNIEnv *env; + _jvm->AttachCurrentThreadAsDaemon((void **)&env, NULL); + for (;;) { + jmethodID id = ring_buffer[rand() % BUFFER_SIZE].load(std::memory_order_relaxed); + if (id != (jmethodID)0) { + get_method_details(id); + } + } + return NULL; +} + +static void JNICALL ClassPrepareCallback(jvmtiEnv *jvmti_env, + JNIEnv *jni_env, + jthread thread, + jclass klass) { + static bool reader_created = false; + static int ring_buffer_idx = 0; + + char *class_name = NULL; + if (jvmti_env->GetClassSignature(klass, &class_name, NULL) != JVMTI_ERROR_NONE) { + return; + } + // We only care MyClass and only one thread loads it + bool is_my_class = strcmp(class_name, "LMyClass;") == 0; + jvmti_env->Deallocate((unsigned char *)class_name); + if (!is_my_class) { + return; + } + + if (!reader_created) { + pthread_t tid; + pthread_create(&tid, NULL, read_ringbuffer, NULL); + reader_created = true; + } + + jint method_count; + jmethodID *methods; + if (jvmti_env->GetClassMethods(klass, &method_count, &methods) == JVMTI_ERROR_NONE) { + ring_buffer[ring_buffer_idx++].store(methods[0], std::memory_order_relaxed); + ring_buffer_idx = ring_buffer_idx % BUFFER_SIZE; + jvmti_env->Deallocate((unsigned char *)methods); + } +} + +JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) { + for (int i = 0; i < BUFFER_SIZE; i++) { + ring_buffer[i].store(0, std::memory_order_relaxed); + } + + jvmtiEventCallbacks callbacks; + jvmtiError error; + + _jvm = jvm; + + if (jvm->GetEnv((void **)&_jvmti, JVMTI_VERSION_1_0) != JNI_OK) { + fprintf(stderr, "Unable to access JVMTI!\n"); + return JNI_ERR; + } + + // Set up the event callbacks + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.ClassPrepare = &ClassPrepareCallback; + + // Register the callbacks + error = _jvmti->SetEventCallbacks(&callbacks, sizeof(callbacks)); + if (error != JVMTI_ERROR_NONE) { + fprintf(stderr, "Error setting event callbacks: %d\n", error); + return JNI_ERR; + } + + // Enable the ClassPrepare event + error = _jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_CLASS_PREPARE, NULL); + if (error != JVMTI_ERROR_NONE) { + fprintf(stderr, "Error enabling ClassPrepare event: %d\n", error); + return JNI_ERR; + } + + return JNI_OK; +}