Today, when a ClassLoaderData is deallocated, its JNIMethodBlocks are kept around, resulting in a slow memory leak. We do this because JVMTI agents can reuse a jmethodID that belongs to a class that has been deallocated. The JVMTI implementation tolerates this by checking if the jmethodID (which is basically a handle to a Method*) contains a NULL pointer. If so, this indicates that the jmethodID was referring to a Method that has since been deallocated. JVMTI APIs will return JVMTI_ERROR_INVALID_ENVIRONMENT instead of proceeding further. E.g.,
jvmti_GetMethodDeclaringClass(jvmtiEnv* env,
jmethodID method,
jclass* declaring_class_ptr) {
...
Method* checked_method = Method::checked_resolve_jmethod_id(method);
if (checked_method == NULL) {
return JVMTI_ERROR_INVALID_METHODID;
}
However, the "regular" JNI APIs (those declared in jni.h) as implemented in HotSpot do not tolerate such invalid jmethodIDs. I have checked all functions under jni.cpp in JDK 16. Every single one of them will crash immediately when an invalid jmethodID is given:
E.g., the following will crash by dereferencing the invalid pointer <m>.
JNI_ENTRY(jobject, jni_ToReflectedMethod(JNIEnv *env, jclass cls, jmethodID method_id, jboolean isStatic))
...
methodHandle m (THREAD, Method::resolve_jmethod_id(method_id));
...
if (m->is_initializer()) {
Therefore, we can conclude that no working JNI code today can use invalid jmethodID without crashing (except for JVMTI agent code). As a result, we can free the JNIMethodBlocks to avoid memory leaks.