Here is a reproduced test case from async profiler:
https://github.com/async-profiler/async-profiler/pull/981
Both G1 and ZGC crash. The stack traces are G1/ZGC concurrent marking or G1 full gc marking.
Main.java:
===========================================================
import java.util.Base64;
public class Main extends Thread {
public static void main(String[] args) throws Exception {
long last = System.nanoTime();
for (int i = 0;; i++) {
CustomClassLoader loader = new CustomClassLoader();
Class<?> k = loader.findClass("TemplateFFFFFFFF");
Object o = k.getDeclaredConstructor().newInstance();
// call gc every ~1 second.
if ((System.nanoTime() - last) >= 1e9) {
System.gc();
last = System.nanoTime();
}
}
}
}
class CustomClassLoader extends ClassLoader {
@Override
public Class findClass(String name) throws ClassNotFoundException {
/*
* Bytecode for:
* public class TemplateFFFFFFFF {
* public void doTemplateFFFFFFFF() {
* return;
* }
* }
*/
byte[] b = Base64.getDecoder()
.decode("yv66vgAAADQADgoAAwALBwAMBwANAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJU" +
"YWJsZQEAEmRvVGVtcGxhdGVGRkZGRkZGRgEAClNvdXJjZUZpbGUBABVUZW1wbGF0ZUZGRkZGRkZG" +
"LmphdmEMAAQABQEAEFRlbXBsYXRlRkZGRkZGRkYBABBqYXZhL2xhbmcvT2JqZWN0ACEAAgADAAAA" +
"AAACAAEABAAFAAEABgAAAB0AAQABAAAABSq3AAGxAAAAAQAHAAAABgABAAAAAQABAAgABQABAAYA" +
"AAAZAAAAAQAAAAGxAAAAAQAHAAAABgABAAAAAwABAAkAAAACAAo=");
return defineClass(name, b, 0, b.length);
}
}
=========================================================
repro.cpp:
=========================================================
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <jvmti.h>
#include <jni.h>
#include <pthread.h>
static jvmtiEnv *jvmti;
static JavaVM *_jvm;
static JNIEnv *_rb_env;
#ifndef WITH_GetClassSignature
#define WITH_GetClassSignature 1
#endif
#ifndef WITH_DeleteLocalRef
#define WITH_DeleteLocalRef 0
#endif
#define BUFFER_SIZE 100000
static size_t ring_buffer[BUFFER_SIZE] = {0};
static volatile int ring_buffer_idx = 0;
static int reader_created = 0;
void *get_method_details(void *arg)
{
jmethodID method = (jmethodID)arg;
jclass method_class;
char *class_name = NULL;
jvmtiError err = JVMTI_ERROR_NONE;
// For JVM 17, 21, 22 calling GetMethodDeclaringClass is enough.
if ((err = jvmti->GetMethodDeclaringClass(method, &method_class)) == 0)
{
if (WITH_DeleteLocalRef)
{
_rb_env->DeleteLocalRef(method_class);
}
if (WITH_GetClassSignature)
{
// JVM 8 needs this to crash
jvmti->GetClassSignature(method_class, &class_name, NULL);
jvmti->Deallocate((unsigned char *)class_name);
}
}
}
void *read_ringbuffer(void *arg)
{
JNIEnv *env;
_jvm->AttachCurrentThread((void **)&env, NULL);
_rb_env = env;
for (;;)
{
size_t id = ring_buffer[rand() % BUFFER_SIZE];
if (id > 0)
{
get_method_details((void *)id);
}
}
}
static void JNICALL ClassPrepareCallback(jvmtiEnv *jvmti_env,
JNIEnv *jni_env,
jthread thread,
jclass klass)
{
if (reader_created == 0)
{
pthread_t tid;
pthread_create(&tid, NULL, read_ringbuffer, NULL);
reader_created = 1;
}
// Get the list of methods
jint method_count;
jmethodID *methods;
if (jvmti_env->GetClassMethods(klass, &method_count, &methods) == JVMTI_ERROR_NONE)
{
for (int i = 0; i < method_count; i++)
{
ring_buffer[ring_buffer_idx++] = (size_t)methods[i];
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)
{
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;
}
=============================================================
Steps to reproduce:
javac Main.java
gcc -shared -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -fPIC repro.cpp -orepro.so
# Low Xmx to pressure GC into unloading classes sooner.
java -agentpath:"$(pwd)/repro.so" -Xmx100m Main