Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8339725

Concurrent GC crashed due to GetMethodDeclaringClass

XMLWordPrintable

    • b16

        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

              lmao Liang Mao
              lmao Liang Mao
              Votes:
              0 Vote for this issue
              Watchers:
              8 Start watching this issue

                Created:
                Updated:
                Resolved: