Skip to content

8339725: Concurrent GC crashed due to GetMethodDeclaringClass #3646

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion make/test/JtregNativeHotspot.gmk
Original file line number Diff line number Diff line change
Expand Up @@ -873,7 +873,7 @@ BUILD_HOTSPOT_JTREG_EXECUTABLES_LIBS_exesigtest := -ljvm

ifeq ($(call isTargetOs, windows), true)
BUILD_HOTSPOT_JTREG_EXECUTABLES_CFLAGS_exeFPRegs := -MT
BUILD_HOTSPOT_JTREG_EXCLUDE += exesigtest.c libterminatedThread.c libTestJNI.c libnativeStack.c exeGetCreatedJavaVMs.c
BUILD_HOTSPOT_JTREG_EXCLUDE += exesigtest.c libterminatedThread.c libTestJNI.c libnativeStack.c exeGetCreatedJavaVMs.c libTestUnloadedClass.cpp
BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libatExit := jvm.lib
else
BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libbootclssearch_agent += -lpthread
Expand Down Expand Up @@ -1511,6 +1511,7 @@ else
BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libterminatedThread += -lpthread
BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libatExit += -ljvm
BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libnativeStack += -lpthread
BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libTestUnloadedClass += -lpthread
BUILD_HOTSPOT_JTREG_EXECUTABLES_LIBS_exeGetCreatedJavaVMs := -ljvm -lpthread
endif

Expand Down
8 changes: 6 additions & 2 deletions src/hotspot/share/prims/jvmtiEnv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2747,7 +2747,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 */
Expand Down Expand Up @@ -2825,7 +2827,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 */

Expand Down
1 change: 1 addition & 0 deletions src/hotspot/share/prims/jvmtiEnvBase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,7 @@ JvmtiEnvBase::vframeForNoProcess(JavaThread* java_thread, jint depth) {
jclass
JvmtiEnvBase::get_jni_class_non_null(Klass* k) {
assert(k != NULL, "k != NULL");
assert(k->is_loader_alive(), "Must be alive");
Thread *thread = Thread::current();
return (jclass)jni_reference(Handle(thread, k->java_mirror()));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {
}
Original file line number Diff line number Diff line change
@@ -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 <atomic>

#include <jvmti.h>
#include <jni.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static jvmtiEnv *_jvmti;
static JavaVM *_jvm;

#define BUFFER_SIZE 100000
static std::atomic<jmethodID> 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;
}