JVM系列(四):java方法的查找过程实现

共 50099字,需浏览 101分钟

 ·

2021-02-24 13:29

走过路过不要错过

点击蓝字关注我们


经过前面几章的简单介绍,我们已经大致了解了jvm的启动框架和执行流程了。不过,这些都是些无关痛痒的问题,几行文字描述一下即可。

所以,今天我们从另一个角度来讲解jvm的一些东西,以便可以更多一点认知。即如题:jvm是如何找到对应的java方法,然后执行的呢?(但是执行太复杂,太重要,我们就不说了。我们单看如何找到对应的java方法吧)

1:回顾核心变量JNIEnv的初始化

如上一篇系列文章中讲到的,jdk执行的核心方法,实际上也是调用jvm或者hotspot的接口方法实现的,这其中有个重要变量,供jdk使用。即:JNIEnv* env 。可见其重要性。我们再来回顾下它的初始化过程。

    //实际上,我们可以通过前面对 JNIEnv **penv 的赋值中查到端倪:    // hotspot/src/share/vm/prims/jni.cpp    ...    // 将jvm信息存储到 penv 中,以备外部使用    *(JNIEnv**)penv = thread->jni_environment();    ...    // 而查看 jni_environment() 方法可知,其由一个类变量 _jni_environment 处理  // share/vm/runtime/thread.hpp  // Returns the jni environment for this thread  JNIEnv* jni_environment()                      { return &_jni_environment; }
// 所以,我们只需找出 _jni_environment 是如何赋值初始化,即可知道如何获取这个关键变量的逻辑了。结果是,在创建JavaThread, 在进行初始化时,便会设置该值。// share/vm/runtime/thread.cppJavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) : Thread()#if INCLUDE_ALL_GCS , _satb_mark_queue(&_satb_mark_queue_set), _dirty_card_queue(&_dirty_card_queue_set)#endif // INCLUDE_ALL_GCS{ if (TraceThreadEvents) { tty->print_cr("creating thread %p", this); } // 初始化线程变量信息, 如 JNIEnv initialize(); _jni_attach_state = _not_attaching_via_jni; set_entry_point(entry_point); // Create the native thread itself. // %note runtime_23 os::ThreadType thr_type = os::java_thread; thr_type = entry_point == &compiler_thread_entry ? os::compiler_thread : os::java_thread; os::create_thread(this, thr_type, stack_sz); _safepoint_visible = false; // The _osthread may be NULL here because we ran out of memory (too many threads active). // We need to throw and OutOfMemoryError - however we cannot do this here because the caller // may hold a lock and all locks must be unlocked before throwing the exception (throwing // the exception consists of creating the exception object & initializing it, initialization // will leave the VM via a JavaCall and then all locks must be unlocked). // // The thread is still suspended when we reach here. Thread must be explicit started // by creator! Furthermore, the thread must also explicitly be added to the Threads list // by calling Threads:add. The reason why this is not done here, is because the thread // object must be fully initialized (take a look at JVM_Start)}
// A JavaThread is a normal Java threadvoid JavaThread::initialize() { // Initialize fields
// Set the claimed par_id to -1 (ie not claiming any par_ids) set_claimed_par_id(-1);
set_saved_exception_pc(NULL); set_threadObj(NULL); _anchor.clear(); set_entry_point(NULL); // 取数jni_functions, 初始化到 _jni_environment set_jni_functions(jni_functions()); set_callee_target(NULL); set_vm_result(NULL); set_vm_result_2(NULL); set_vframe_array_head(NULL); set_vframe_array_last(NULL); set_deferred_locals(NULL); set_deopt_mark(NULL); set_deopt_nmethod(NULL); clear_must_deopt_id(); set_monitor_chunks(NULL); set_next(NULL); set_thread_state(_thread_new);#if INCLUDE_NMT set_recorder(NULL);#endif _terminated = _not_terminated; _privileged_stack_top = NULL; _array_for_gc = NULL; _suspend_equivalent = false; _in_deopt_handler = 0; _doing_unsafe_access = false; _stack_guard_state = stack_guard_unused; (void)const_cast(_exception_oop = NULL); _exception_pc = 0; _exception_handler_pc = 0; _is_method_handle_return = 0; _jvmti_thread_state= NULL; _should_post_on_exceptions_flag = JNI_FALSE; _jvmti_get_loaded_classes_closure = NULL; _interp_only_mode = 0; _special_runtime_exit_condition = _no_async_condition; _pending_async_exception = NULL; _thread_stat = NULL; _thread_stat = new ThreadStatistics(); _blocked_on_compilation = false; _jni_active_critical = 0; _do_not_unlock_if_synchronized = false; _cached_monitor_info = NULL; _parker = Parker::Allocate(this) ;
#ifndef PRODUCT _jmp_ring_index = 0; for (int ji = 0 ; ji < jump_ring_buffer_size ; ji++ ) { record_jump(NULL, NULL, NULL, 0); }#endif /* PRODUCT */
set_thread_profiler(NULL); if (FlatProfiler::is_active()) { // This is where we would decide to either give each thread it's own profiler // or use one global one from FlatProfiler, // or up to some count of the number of profiled threads, etc. ThreadProfiler* pp = new ThreadProfiler(); pp->engage(); set_thread_profiler(pp); }
// Setup safepoint state info for this thread ThreadSafepointState::create(this);
debug_only(_java_call_counter = 0);
// JVMTI PopFrame support _popframe_condition = popframe_inactive; _popframe_preserved_args = NULL; _popframe_preserved_args_size = 0;
pd_initialize();}
// Returns the function structurestruct JNINativeInterface_* jni_functions() {#if INCLUDE_JNI_CHECK if (CheckJNICalls) return jni_functions_check();#endif // INCLUDE_JNI_CHECK return &jni_NativeInterface;} // thread.hpp //JNI functiontable getter/setter for JVMTI jni function table interception API. void set_jni_functions(struct JNINativeInterface_* functionTable) { _jni_environment.functions = functionTable; }

所以,核心的初始化变成了 jni_NativeInterface 的具体值问题了。刚好我们可以通过这个方法去这个 JNIEnv 都定义了啥。这对于我们以后的分析工作有非常大的帮助。

// jni.cpp// Structure containing all jni functionsstruct JNINativeInterface_ jni_NativeInterface = {    NULL,    NULL,    NULL,
NULL,
jni_GetVersion,
jni_DefineClass, jni_FindClass,
jni_FromReflectedMethod, jni_FromReflectedField,
jni_ToReflectedMethod,
jni_GetSuperclass, jni_IsAssignableFrom,
jni_ToReflectedField,
jni_Throw, jni_ThrowNew, jni_ExceptionOccurred, jni_ExceptionDescribe, jni_ExceptionClear, jni_FatalError,
jni_PushLocalFrame, jni_PopLocalFrame,
jni_NewGlobalRef, jni_DeleteGlobalRef, jni_DeleteLocalRef, jni_IsSameObject,
jni_NewLocalRef, jni_EnsureLocalCapacity,
jni_AllocObject, jni_NewObject, jni_NewObjectV, jni_NewObjectA,
jni_GetObjectClass, jni_IsInstanceOf,
jni_GetMethodID,
jni_CallObjectMethod, jni_CallObjectMethodV, jni_CallObjectMethodA, jni_CallBooleanMethod, jni_CallBooleanMethodV, jni_CallBooleanMethodA, jni_CallByteMethod, jni_CallByteMethodV, jni_CallByteMethodA, jni_CallCharMethod, jni_CallCharMethodV, jni_CallCharMethodA, jni_CallShortMethod, jni_CallShortMethodV, jni_CallShortMethodA, jni_CallIntMethod, jni_CallIntMethodV, jni_CallIntMethodA, jni_CallLongMethod, jni_CallLongMethodV, jni_CallLongMethodA, jni_CallFloatMethod, jni_CallFloatMethodV, jni_CallFloatMethodA, jni_CallDoubleMethod, jni_CallDoubleMethodV, jni_CallDoubleMethodA, jni_CallVoidMethod, jni_CallVoidMethodV, jni_CallVoidMethodA,
jni_CallNonvirtualObjectMethod, jni_CallNonvirtualObjectMethodV, jni_CallNonvirtualObjectMethodA, jni_CallNonvirtualBooleanMethod, jni_CallNonvirtualBooleanMethodV, jni_CallNonvirtualBooleanMethodA, jni_CallNonvirtualByteMethod, jni_CallNonvirtualByteMethodV, jni_CallNonvirtualByteMethodA, jni_CallNonvirtualCharMethod, jni_CallNonvirtualCharMethodV, jni_CallNonvirtualCharMethodA, jni_CallNonvirtualShortMethod, jni_CallNonvirtualShortMethodV, jni_CallNonvirtualShortMethodA, jni_CallNonvirtualIntMethod, jni_CallNonvirtualIntMethodV, jni_CallNonvirtualIntMethodA, jni_CallNonvirtualLongMethod, jni_CallNonvirtualLongMethodV, jni_CallNonvirtualLongMethodA, jni_CallNonvirtualFloatMethod, jni_CallNonvirtualFloatMethodV, jni_CallNonvirtualFloatMethodA, jni_CallNonvirtualDoubleMethod, jni_CallNonvirtualDoubleMethodV, jni_CallNonvirtualDoubleMethodA, jni_CallNonvirtualVoidMethod, jni_CallNonvirtualVoidMethodV, jni_CallNonvirtualVoidMethodA,
jni_GetFieldID,
jni_GetObjectField, jni_GetBooleanField, jni_GetByteField, jni_GetCharField, jni_GetShortField, jni_GetIntField, jni_GetLongField, jni_GetFloatField, jni_GetDoubleField,
jni_SetObjectField, jni_SetBooleanField, jni_SetByteField, jni_SetCharField, jni_SetShortField, jni_SetIntField, jni_SetLongField, jni_SetFloatField, jni_SetDoubleField,
jni_GetStaticMethodID,
jni_CallStaticObjectMethod, jni_CallStaticObjectMethodV, jni_CallStaticObjectMethodA, jni_CallStaticBooleanMethod, jni_CallStaticBooleanMethodV, jni_CallStaticBooleanMethodA, jni_CallStaticByteMethod, jni_CallStaticByteMethodV, jni_CallStaticByteMethodA, jni_CallStaticCharMethod, jni_CallStaticCharMethodV, jni_CallStaticCharMethodA, jni_CallStaticShortMethod, jni_CallStaticShortMethodV, jni_CallStaticShortMethodA, jni_CallStaticIntMethod, jni_CallStaticIntMethodV, jni_CallStaticIntMethodA, jni_CallStaticLongMethod, jni_CallStaticLongMethodV, jni_CallStaticLongMethodA, jni_CallStaticFloatMethod, jni_CallStaticFloatMethodV, jni_CallStaticFloatMethodA, jni_CallStaticDoubleMethod, jni_CallStaticDoubleMethodV, jni_CallStaticDoubleMethodA, jni_CallStaticVoidMethod, jni_CallStaticVoidMethodV, jni_CallStaticVoidMethodA,
jni_GetStaticFieldID,
jni_GetStaticObjectField, jni_GetStaticBooleanField, jni_GetStaticByteField, jni_GetStaticCharField, jni_GetStaticShortField, jni_GetStaticIntField, jni_GetStaticLongField, jni_GetStaticFloatField, jni_GetStaticDoubleField,
jni_SetStaticObjectField, jni_SetStaticBooleanField, jni_SetStaticByteField, jni_SetStaticCharField, jni_SetStaticShortField, jni_SetStaticIntField, jni_SetStaticLongField, jni_SetStaticFloatField, jni_SetStaticDoubleField,
jni_NewString, jni_GetStringLength, jni_GetStringChars, jni_ReleaseStringChars,
jni_NewStringUTF, jni_GetStringUTFLength, jni_GetStringUTFChars, jni_ReleaseStringUTFChars,
jni_GetArrayLength,
jni_NewObjectArray, jni_GetObjectArrayElement, jni_SetObjectArrayElement,
jni_NewBooleanArray, jni_NewByteArray, jni_NewCharArray, jni_NewShortArray, jni_NewIntArray, jni_NewLongArray, jni_NewFloatArray, jni_NewDoubleArray,
jni_GetBooleanArrayElements, jni_GetByteArrayElements, jni_GetCharArrayElements, jni_GetShortArrayElements, jni_GetIntArrayElements, jni_GetLongArrayElements, jni_GetFloatArrayElements, jni_GetDoubleArrayElements,
jni_ReleaseBooleanArrayElements, jni_ReleaseByteArrayElements, jni_ReleaseCharArrayElements, jni_ReleaseShortArrayElements, jni_ReleaseIntArrayElements, jni_ReleaseLongArrayElements, jni_ReleaseFloatArrayElements, jni_ReleaseDoubleArrayElements,
jni_GetBooleanArrayRegion, jni_GetByteArrayRegion, jni_GetCharArrayRegion, jni_GetShortArrayRegion, jni_GetIntArrayRegion, jni_GetLongArrayRegion, jni_GetFloatArrayRegion, jni_GetDoubleArrayRegion,
jni_SetBooleanArrayRegion, jni_SetByteArrayRegion, jni_SetCharArrayRegion, jni_SetShortArrayRegion, jni_SetIntArrayRegion, jni_SetLongArrayRegion, jni_SetFloatArrayRegion, jni_SetDoubleArrayRegion,
jni_RegisterNatives, jni_UnregisterNatives,
jni_MonitorEnter, jni_MonitorExit,
jni_GetJavaVM,
jni_GetStringRegion, jni_GetStringUTFRegion,
jni_GetPrimitiveArrayCritical, jni_ReleasePrimitiveArrayCritical,
jni_GetStringCritical, jni_ReleaseStringCritical,
jni_NewWeakGlobalRef, jni_DeleteWeakGlobalRef,
jni_ExceptionCheck,
jni_NewDirectByteBuffer, jni_GetDirectBufferAddress, jni_GetDirectBufferCapacity,
// New 1_6 features
jni_GetObjectRefType};


以上就是 JNIEnv* env 变量的设值过程了,它借助于java线程的创建时机进行初始化。而后续的使用中,几乎都会仰仗它来运行,可见其重要性。

但总结一下,这里面提供的接口,实际上都是一些非常基础的操作,比如变量新建,初始化,异常处理,锁处理,native注册等。类型实际并不多。这也提示了我们一点,越是基础的东西,实际上越不会那么复杂。它更多的是做好抽象工作,打好基础,比什么都好。

2. main方法的查找实现

要谈其他方法,着实也太泛了。因为,你可以定义这个方法,他可以定义一个别的方法。这里面的特性就太难找了。但,对于每个java应用的启动,都会去加载main()方法执行,所以,以这个main()方法的查找为出发点,定然能找到些端倪来。

我们先来看看main()的调用地方如何:

// share/bin/java.c// 加载 main 函数类// 通过引入 JavaMain(), 接入java方法// #define JNICALL __stdcallint JNICALLJavaMain(void * _args){    JavaMainArgs *args = (JavaMainArgs *)_args;    int argc = args->argc;    char **argv = args->argv;    int mode = args->mode;    char *what = args->what;    // 一些jvm的调用实例,在之前的步骤中,通过加载相应动态链接方法,保存起来的    /**      * ifn->CreateJavaVM =     *   (void *)GetProcAddress(handle, "JNI_CreateJavaVM");     * ifn->GetDefaultJavaVMInitArgs =     *   (void *)GetProcAddress(handle, "JNI_GetDefaultJavaVMInitArgs");     */    InvocationFunctions ifn = args->ifn;    JavaVM *vm = 0;    JNIEnv *env = 0;    jclass mainClass = NULL;    jclass appClass = NULL; // actual application class being launched    jmethodID mainID;    jobjectArray mainArgs;    int ret = 0;    jlong start, end;    // collector    RegisterThread();    /* Initialize the virtual machine */    start = CounterGet();    // 重点1:初始化jvm,失败则退出    // 此处会将重要变量 *env 进程初始化,从而使后续可用    if (!InitializeJVM(&vm, &env, &ifn)) {        JLI_ReportErrorMessage(JVM_ERROR1);        exit(1);    }    // jvm检查完毕,如果只是一些展示类请求,则展示信息后,退出jvm    if (showSettings != NULL) {        ShowSettings(env, showSettings);        /**         * 宏是神奇的操作,此处 *env 直接引用#define CHECK_EXCEPTION_LEAVE(CEL_return_value) \    do { \        if ((*env)->ExceptionOccurred(env)) { \            JLI_ReportExceptionDescription(env); \            ret = (CEL_return_value); \            LEAVE(); \        } \    } while (JNI_FALSE)         */        CHECK_EXCEPTION_LEAVE(1);    }    // 调用 LEAVE() 方法的目的在于主动销毁jvm线程    // 且退出当前方法调用,即 LEAVE() 后方法不再被执行/* * Always detach the main thread so that it appears to have ended when * the application's main method exits.  This will invoke the * uncaught exception handler machinery if main threw an * exception.  An uncaught exception handler cannot change the * launcher's return code except by calling System.exit. * * Wait for all non-daemon threads to end, then destroy the VM. * This will actually create a trivial new Java waiter thread * named "DestroyJavaVM", but this will be seen as a different * thread from the one that executed main, even though they are * the same C thread.  This allows mainThread.join() and * mainThread.isAlive() to work as expected. */    /**     *     * #define LEAVE() \    do { \        if ((*vm)->DetachCurrentThread(vm) != JNI_OK) { \            JLI_ReportErrorMessage(JVM_ERROR2); \            ret = 1; \        } \        if (JNI_TRUE) { \            (*vm)->DestroyJavaVM(vm); \            return ret; \        } \    } while (JNI_FALSE)     */    if (printVersion || showVersion) {        PrintJavaVersion(env, showVersion);        CHECK_EXCEPTION_LEAVE(0);        if (printVersion) {            LEAVE();        }    }    /* If the user specified neither a class name nor a JAR file */    if (printXUsage || printUsage || what == 0 || mode == LM_UNKNOWN) {        PrintUsage(env, printXUsage);        CHECK_EXCEPTION_LEAVE(1);        LEAVE();    }    // 释放内存    FreeKnownVMs();  /* after last possible PrintUsage() */    if (JLI_IsTraceLauncher()) {        end = CounterGet();        JLI_TraceLauncher("%ld micro seconds to InitializeJVM\n",               (long)(jint)Counter2Micros(end-start));    }    /* At this stage, argc/argv have the application's arguments */    if (JLI_IsTraceLauncher()){        int i;        printf("%s is '%s'\n", launchModeNames[mode], what);        printf("App's argc is %d\n", argc);        for (i=0; i < argc; i++) {            printf("    argv[%2d] = '%s'\n", i, argv[i]);        }    }    ret = 1;    /*     * Get the application's main class.     *     * See bugid 5030265.  The Main-Class name has already been parsed     * from the manifest, but not parsed properly for UTF-8 support.     * Hence the code here ignores the value previously extracted and     * uses the pre-existing code to reextract the value.  This is     * possibly an end of release cycle expedient.  However, it has     * also been discovered that passing some character sets through     * the environment has "strange" behavior on some variants of     * Windows.  Hence, maybe the manifest parsing code local to the     * launcher should never be enhanced.     *     * Hence, future work should either:     *     1)   Correct the local parsing code and verify that the     *          Main-Class attribute gets properly passed through     *          all environments,     *     2)   Remove the vestages of maintaining main_class through     *          the environment (and remove these comments).     *     * This method also correctly handles launching existing JavaFX     * applications that may or may not have a Main-Class manifest entry.     */    // 重点2:加载 main 指定的class类    mainClass = LoadMainClass(env, mode, what);    CHECK_EXCEPTION_NULL_LEAVE(mainClass);    /*     * In some cases when launching an application that needs a helper, e.g., a     * JavaFX application with no main method, the mainClass will not be the     * applications own main class but rather a helper class. To keep things     * consistent in the UI we need to track and report the application main class.     */    appClass = GetApplicationClass(env);    NULL_CHECK_RETURN_VALUE(appClass, -1);    /*     * PostJVMInit uses the class name as the application name for GUI purposes,     * for example, on OSX this sets the application name in the menu bar for     * both SWT and JavaFX. So we'll pass the actual application class here     * instead of mainClass as that may be a launcher or helper class instead     * of the application class.     */    // 加载main() 方法前执行初始化    PostJVMInit(env, appClass, vm);    CHECK_EXCEPTION_LEAVE(1);    /*     * The LoadMainClass not only loads the main class, it will also ensure     * that the main method's signature is correct, therefore further checking     * is not required. The main method is invoked here so that extraneous java     * stacks are not in the application stack trace.     */    // 重点3:执行 main(args[]) java方法    // 获取main()方法id, main(String[] args)    mainID = (*env)->GetStaticMethodID(env, mainClass, "main",                                       "([Ljava/lang/String;)V");    CHECK_EXCEPTION_NULL_LEAVE(mainID);    /* Build platform specific argument array */    // 构建args[] 参数    mainArgs = CreateApplicationArgs(env, argv, argc);    CHECK_EXCEPTION_NULL_LEAVE(mainArgs);    /* Invoke main method. */    // 调用java实现的main()方法    // XX:: 重要实现    (*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);    /*     * The launcher's exit code (in the absence of calls to     * System.exit) will be non-zero if main threw an exception.     */    ret = (*env)->ExceptionOccurred(env) == NULL ? 0 : 1;    LEAVE();}

JVM的初始化,我们在上篇系列文章中已窥得简要。这篇,我们就以 *env 作为入口进行。因为jvm初始化完成后,就会给 *env 的赋值。

2.1. GetStaticMethodID 的实现

而,加载main()方法,最核心的就是上面最后几行:

    // 获取main()方法id, main(String[] args)    mainID = (*env)->GetStaticMethodID(env, mainClass, "main",                                       "([Ljava/lang/String;)V");    CHECK_EXCEPTION_NULL_LEAVE(mainID);    /* Build platform specific argument array */    // 构建args[] 参数    mainArgs = CreateApplicationArgs(env, argv, argc);    CHECK_EXCEPTION_NULL_LEAVE(mainArgs);    /* Invoke main method. */    // 调用java实现的main()方法    // XX:: 重要实现    (*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);    /*     * The launcher's exit code (in the absence of calls to     * System.exit) will be non-zero if main threw an exception.     */    ret = (*env)->ExceptionOccurred(env) == NULL ? 0 : 1;

很明显,我们的目的就是看jvm如何找到main()方法,这也是执行main()逻辑的第一步工作。下面来细聊下,它使用的是 (*env)->GetStaticMethodID(), 而这个方法,在上一节中,我们可以看到其实现为:jni_GetStaticMethodID 。所以,知道这个 jni_GetStaticMethodID 的实现就知道了如何查找java静态方法了。

// share/vm/prims/jni.cppJNI_ENTRY(jmethodID, jni_GetStaticMethodID(JNIEnv *env, jclass clazz,          const char *name, const char *sig))  JNIWrapper("GetStaticMethodID");#ifndef USDT2  DTRACE_PROBE4(hotspot_jni, GetStaticMethodID__entry, env, clazz, name, sig);#else /* USDT2 */  HOTSPOT_JNI_GETSTATICMETHODID_ENTRY(                                      env, (char *) clazz, (char *) name, (char *)sig);#endif /* USDT2 */  jmethodID ret = get_method_id(env, clazz, name, sig, true, thread);#ifndef USDT2  DTRACE_PROBE1(hotspot_jni, GetStaticMethodID__return, ret);#else /* USDT2 */  HOTSPOT_JNI_GETSTATICMETHODID_RETURN(                                       (uintptr_t) ret);#endif /* USDT2 */  return ret;JNI_END


我们通过这个实现,能看到什么呢?好像什么也看不懂。实际上是因为,其中有太多的宏定义了,要想读懂这代码,必须将宏定义展开。而这些宏,基本都是是在 interfaceSupport.hpp 中定义的。

下面我们来看下 JNI_ENTRY|JNI_END 的定义拆解:

// share/vm/runtime/interfaceSupport.hpp// JNI_ENTRY 的定义,又依赖于 JNI_ENTRY_NO_PRESERVE 的定义#define JNI_ENTRY(result_type, header)                               \    JNI_ENTRY_NO_PRESERVE(result_type, header)                       \    WeakPreserveExceptionMark __wem(thread);// JNI_ENTRY_NO_PRESERVE 的定义,又依赖于 VM_ENTRY_BASE 的定义#define JNI_ENTRY_NO_PRESERVE(result_type, header)             \extern "C" {                                                         \  result_type JNICALL header {                                \    JavaThread* thread=JavaThread::thread_from_jni_environment(env); \    assert( !VerifyJNIEnvThread || (thread == Thread::current()), "JNIEnv is only valid in same thread"); \    ThreadInVMfromNative __tiv(thread);                              \    debug_only(VMNativeEntryWrapper __vew;)                          \    VM_ENTRY_BASE(result_type, header, thread)// VM_ENTRY_BASE 的定义#define VM_ENTRY_BASE(result_type, header, thread)                   \  TRACE_CALL(result_type, header)                                    \  HandleMarkCleaner __hm(thread);                                    \  Thread* THREAD = thread;                                           \  os::verify_stack_alignment();                                      \  /* begin of body */
// Close the routine and the extern "C"#define JNI_END } }

此时,如上的函数实现可以转换为:

extern "C" {                                                           jmethodID JNICALL header {                                    JavaThread* thread=JavaThread::thread_from_jni_environment(env);     assert( !VerifyJNIEnvThread || (thread == Thread::current()), "JNIEnv is only valid in same thread");     ThreadInVMfromNative __tiv(thread);                                  debug_only(VMNativeEntryWrapper __vew;)                              TRACE_CALL(jmethodID, jni_GetStaticMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig))                                        HandleMarkCleaner __hm(thread);                                        Thread* THREAD = thread;                                               os::verify_stack_alignment();                                                         WeakPreserveExceptionMark __wem(thread);  // 默认为空  JNIWrapper("GetStaticMethodID");#ifndef USDT2  DTRACE_PROBE4(hotspot_jni, GetStaticMethodID__entry, env, clazz, name, sig);#else /* USDT2 */  // 默认为空, 在 hotspot/src/share/vm/utilities/dtrace_usdt2_disabled.hpp 中定义  HOTSPOT_JNI_GETSTATICMETHODID_ENTRY(                                      env, (char *) clazz, (char *) name, (char *)sig);#endif /* USDT2 */  // 核心查找方法  jmethodID ret = get_method_id(env, clazz, name, sig, true, thread);#ifndef USDT2  DTRACE_PROBE1(hotspot_jni, GetStaticMethodID__return, ret);#else /* USDT2 */  // 默认为空  HOTSPOT_JNI_GETSTATICMETHODID_RETURN(                                       (uintptr_t) ret);#endif /* USDT2 */  return ret;} }

经过这一层层的宏展开,工作就变得清晰起来,重点在于 get_method_id() 了。

// jni.cpp 根据方法签名,找到方法idstatic jmethodID get_method_id(JNIEnv *env, jclass clazz, const char *name_str,                               const char *sig, bool is_static, TRAPS) {  // %%%% This code should probably just call into a method in the LinkResolver  //  // The class should have been loaded (we have an instance of the class  // passed in) so the method and signature should already be in the symbol  // table.  If they're not there, the method doesn't exist.  const char *name_to_probe = (name_str == NULL)                        ? vmSymbols::object_initializer_name()->as_C_string()                        : name_str;  TempNewSymbol name = SymbolTable::probe(name_to_probe, (int)strlen(name_to_probe));  // sig如: "([Ljava/lang/String;)V"  TempNewSymbol signature = SymbolTable::probe(sig, (int)strlen(sig));
if (name == NULL || signature == NULL) { THROW_MSG_0(vmSymbols::java_lang_NoSuchMethodError(), name_str); }
// Throw a NoSuchMethodError exception if we have an instance of a // primitive java.lang.Class if (java_lang_Class::is_primitive(JNIHandles::resolve_non_null(clazz))) { THROW_MSG_0(vmSymbols::java_lang_NoSuchMethodError(), name_str); } // 初始化类实例 KlassHandle klass(THREAD, java_lang_Class::as_Klass(JNIHandles::resolve_non_null(clazz)));
// Make sure class is linked and initialized before handing id's out to // Method*s. klass()->initialize(CHECK_NULL);
Method* m; // "main" // "" "" if (name == vmSymbols::object_initializer_name() || name == vmSymbols::class_initializer_name()) { // Never search superclasses for constructors if (klass->oop_is_instance()) { m = InstanceKlass::cast(klass())->find_method(name, signature); } else { m = NULL; } } else { // 只是在本类中进行方法id查找 m = klass->lookup_method(name, signature); if (m == NULL && klass->oop_is_instance()) { m = InstanceKlass::cast(klass())->lookup_method_in_ordered_interfaces(name, signature); } } if (m == NULL || (m->is_static() != is_static)) { THROW_MSG_0(vmSymbols::java_lang_NoSuchMethodError(), name_str); } // 返回id return m->jmethod_id();}// share/vm/oops/klass.cpp public: Method* lookup_method(Symbol* name, Symbol* signature) const { return uncached_lookup_method(name, signature); }
Method* Klass::uncached_lookup_method(Symbol* name, Symbol* signature) const {#ifdef ASSERT tty->print_cr("Error: uncached_lookup_method called on a klass oop." " Likely error: reflection method does not correctly" " wrap return value in a mirror object.");#endif ShouldNotReachHere(); return NULL;} // oops/method.hpp // Get this method's jmethodID -- allocate if it doesn't exist jmethodID jmethod_id() { methodHandle this_h(this); return InstanceKlass::get_jmethod_id(method_holder(), this_h); }

// oops/instanceKlass.cpp// Lookup or create a jmethodID.// This code is called by the VMThread and JavaThreads so the// locking has to be done very carefully to avoid deadlocks// and/or other cache consistency problems.//jmethodID InstanceKlass::get_jmethod_id(instanceKlassHandle ik_h, methodHandle method_h) { size_t idnum = (size_t)method_h->method_idnum(); jmethodID* jmeths = ik_h->methods_jmethod_ids_acquire(); size_t length = 0; jmethodID id = NULL;
// We use a double-check locking idiom here because this cache is // performance sensitive. In the normal system, this cache only // transitions from NULL to non-NULL which is safe because we use // release_set_methods_jmethod_ids() to advertise the new cache. // A partially constructed cache should never be seen by a racing // thread. We also use release_store_ptr() to save a new jmethodID // in the cache so a partially constructed jmethodID should never be // seen either. Cache reads of existing jmethodIDs proceed without a // lock, but cache writes of a new jmethodID requires uniqueness and // creation of the cache itself requires no leaks so a lock is // generally acquired in those two cases. // // If the RedefineClasses() API has been used, then this cache can // grow and we'll have transitions from non-NULL to bigger non-NULL. // Cache creation requires no leaks and we require safety between all // cache accesses and freeing of the old cache so a lock is generally // acquired when the RedefineClasses() API has been used.
if (jmeths != NULL) { // the cache already exists if (!ik_h->idnum_can_increment()) { // the cache can't grow so we can just get the current values get_jmethod_id_length_value(jmeths, idnum, &length, &id); } else { // cache can grow so we have to be more careful if (Threads::number_of_threads() == 0 || SafepointSynchronize::is_at_safepoint()) { // we're single threaded or at a safepoint - no locking needed get_jmethod_id_length_value(jmeths, idnum, &length, &id); } else { MutexLocker ml(JmethodIdCreation_lock); get_jmethod_id_length_value(jmeths, idnum, &length, &id); } } } // implied else: // we need to allocate a cache so default length and id values are good
if (jmeths == NULL || // no cache yet length <= idnum || // cache is too short id == NULL) { // cache doesn't contain entry
// This function can be called by the VMThread so we have to do all // things that might block on a safepoint before grabbing the lock. // Otherwise, we can deadlock with the VMThread or have a cache // consistency issue. These vars keep track of what we might have // to free after the lock is dropped. jmethodID to_dealloc_id = NULL; jmethodID* to_dealloc_jmeths = NULL;
// may not allocate new_jmeths or use it if we allocate it jmethodID* new_jmeths = NULL; if (length <= idnum) { // allocate a new cache that might be used size_t size = MAX2(idnum+1, (size_t)ik_h->idnum_allocated_count()); new_jmeths = NEW_C_HEAP_ARRAY(jmethodID, size+1, mtClass); memset(new_jmeths, 0, (size+1)*sizeof(jmethodID)); // cache size is stored in element[0], other elements offset by one new_jmeths[0] = (jmethodID)size; }
// allocate a new jmethodID that might be used jmethodID new_id = NULL; if (method_h->is_old() && !method_h->is_obsolete()) { // The method passed in is old (but not obsolete), we need to use the current version Method* current_method = ik_h->method_with_idnum((int)idnum); assert(current_method != NULL, "old and but not obsolete, so should exist"); new_id = Method::make_jmethod_id(ik_h->class_loader_data(), current_method); } else { // It is the current version of the method or an obsolete method, // use the version passed in new_id = Method::make_jmethod_id(ik_h->class_loader_data(), method_h()); }
if (Threads::number_of_threads() == 0 || SafepointSynchronize::is_at_safepoint()) { // we're single threaded or at a safepoint - no locking needed id = get_jmethod_id_fetch_or_update(ik_h, idnum, new_id, new_jmeths, &to_dealloc_id, &to_dealloc_jmeths); } else { MutexLocker ml(JmethodIdCreation_lock); id = get_jmethod_id_fetch_or_update(ik_h, idnum, new_id, new_jmeths, &to_dealloc_id, &to_dealloc_jmeths); }
// The lock has been dropped so we can free resources. // Free up either the old cache or the new cache if we allocated one. if (to_dealloc_jmeths != NULL) { FreeHeap(to_dealloc_jmeths); } // free up the new ID since it wasn't needed if (to_dealloc_id != NULL) { Method::destroy_jmethod_id(ik_h->class_loader_data(), to_dealloc_id); } } return id;}


查找 methodID的实现就挖到这里吧,拆不下去了,尴尬。

但有一点很明了,就是查找methodID是在mainClass实例中进行的。那么,mainClass又是如何查找到的,我们需要看下。这个要从 LoadMainClass()说起。

2.2. LoadMainClass 查找启动类

上一节我们找到了方法id, 但却未找到类。所以,得重头开始再来。

/* * Loads a class and verifies that the main class is present and it is ok to * call it for more details refer to the java implementation. */static jclassLoadMainClass(JNIEnv *env, int mode, char *name){    jmethodID mid;    jstring str;    jobject result;    jlong start, end;    // sun/launcher/LauncherHelper    jclass cls = GetLauncherHelperClass(env);    NULL_CHECK0(cls);    if (JLI_IsTraceLauncher()) {        start = CounterGet();    }    // checkAndLoadMain(String) 方法作为中间main()调用    NULL_CHECK0(mid = (*env)->GetStaticMethodID(env, cls,                "checkAndLoadMain",                "(ZILjava/lang/String;)Ljava/lang/Class;"));    str = NewPlatformString(env, name);    CHECK_JNI_RETURN_0(        result = (*env)->CallStaticObjectMethod(            env, cls, mid, USE_STDERR, mode, str));    if (JLI_IsTraceLauncher()) {        end   = CounterGet();        printf("%ld micro seconds to load main class\n",               (long)(jint)Counter2Micros(end-start));        printf("----%s----\n", JLDEBUG_ENV_ENTRY);    }    return (jclass)result;}    jclassGetLauncherHelperClass(JNIEnv *env){    if (helperClass == NULL) {        // 查找 helplerClass, 并缓存        NULL_CHECK0(helperClass = FindBootStrapClass(env,                "sun/launcher/LauncherHelper"));    }    return helperClass;}// solaris/bin/java_md_common.c// 查找启动类jclassFindBootStrapClass(JNIEnv *env, const char* classname){    // 先找到jvm的 JVM_FindClassFromBootLoader 函数地址,然后调用即可   if (findBootClass == NULL) {       findBootClass = (FindClassFromBootLoader_t *)dlsym(RTLD_DEFAULT,          "JVM_FindClassFromBootLoader");       if (findBootClass == NULL) {           JLI_ReportErrorMessage(DLL_ERROR4,               "JVM_FindClassFromBootLoader");           return NULL;       }   }   return findBootClass(env, classname);}

具体怎么调用,我们略去不说。但如何查找启动类,可以一起来看看。即 JVM_FindClassFromBootLoader。

// jvm.cpp// Returns a class loaded by the bootstrap class loader; or null// if not found.  ClassNotFoundException is not thrown.//// Rationale behind JVM_FindClassFromBootLoader// a> JVM_FindClassFromClassLoader was never exported in the export tables.// b> because of (a) java.dll has a direct dependecy on the  unexported//    private symbol "_JVM_FindClassFromClassLoader@20".// c> the launcher cannot use the private symbol as it dynamically opens//    the entry point, so if something changes, the launcher will fail//    unexpectedly at runtime, it is safest for the launcher to dlopen a//    stable exported interface.// d> re-exporting JVM_FindClassFromClassLoader as public, will cause its//    signature to change from _JVM_FindClassFromClassLoader@20 to//    JVM_FindClassFromClassLoader and will not be backward compatible//    with older JDKs.// Thus a public/stable exported entry point is the right solution,// public here means public in linker semantics, and is exported only// to the JDK, and is not intended to be a public API.
JVM_ENTRY(jclass, JVM_FindClassFromBootLoader(JNIEnv* env, const char* name)) JVMWrapper2("JVM_FindClassFromBootLoader %s", name);
// Java libraries should ensure that name is never null... // 类名称最长不超过65535 if (name == NULL || (int)strlen(name) > Symbol::max_length()) { // It's impossible to create this class; the name cannot fit // into the constant pool. return NULL; } // 常量池检查 // 创建启动类实例 TempNewSymbol h_name = SymbolTable::new_symbol(name, CHECK_NULL); Klass* k = SystemDictionary::resolve_or_null(h_name, CHECK_NULL); if (k == NULL) { return NULL; }
if (TraceClassResolution) { trace_class_resolution(k); } // 创建jclass版本实例返回 return (jclass) JNIHandles::make_local(env, k->java_mirror());JVM_END


整个方法定义,除去各复杂的宏定义,基本还是逻辑比较清晰的。分三步走:1. 从常量池拿类名信息;2. 查找类信息实例化Klass;3. 转换为jclass返回。


2.3. 添加或查找常量池字符

在查找启动类时,看到有常量池的处理,这也是每个类的初始化时必须的过程,所以来看看常量池的使用吧。

// share/vm/classfile/symbolTable.hpp  // Symbol creation  static Symbol* new_symbol(const char* utf8_buffer, int length, TRAPS) {    assert(utf8_buffer != NULL, "just checking");    return lookup(utf8_buffer, length, THREAD);  }
// symbolTable.cpp// We take care not to be blocking while holding the// SymbolTable_lock. Otherwise, the system might deadlock, since the// symboltable is used during compilation (VM_thread) The lock free// synchronization is simplified by the fact that we do not delete// entries in the symbol table during normal execution (only during// safepoints).
Symbol* SymbolTable::lookup(const char* name, int len, TRAPS) { unsigned int hashValue = hash_symbol(name, len); int index = the_table()->hash_to_index(hashValue);
Symbol* s = the_table()->lookup(index, name, len, hashValue);
// Found if (s != NULL) return s; // 上锁添加常量池 // Grab SymbolTable_lock first. MutexLocker ml(SymbolTable_lock, THREAD);
// Otherwise, add to symbol to table return the_table()->basic_add(index, (u1*)name, len, hashValue, true, CHECK_NULL);}// This version of basic_add adds symbols in batch from the constant pool// parsing.bool SymbolTable::basic_add(ClassLoaderData* loader_data, constantPoolHandle cp, int names_count, const char** names, int* lengths, int* cp_indices, unsigned int* hashValues, TRAPS) {
// Check symbol names are not too long. If any are too long, don't add any. for (int i = 0; i< names_count; i++) { if (lengths[i] > Symbol::max_length()) { THROW_MSG_0(vmSymbols::java_lang_InternalError(), "name is too long to represent"); } }
// Cannot hit a safepoint in this function because the "this" pointer can move. No_Safepoint_Verifier nsv;
for (int i=0; i // Check if the symbol table has been rehashed, if so, need to recalculate // the hash value. unsigned int hashValue; if (use_alternate_hashcode()) { hashValue = hash_symbol(names[i], lengths[i]); } else { hashValue = hashValues[i]; } // Since look-up was done lock-free, we need to check if another // thread beat us in the race to insert the symbol. int index = hash_to_index(hashValue); Symbol* test = lookup(index, names[i], lengths[i], hashValue); if (test != NULL) { // A race occurred and another thread introduced the symbol, this one // will be dropped and collected. Use test instead. cp->symbol_at_put(cp_indices[i], test); assert(test->refcount() != 0, "lookup should have incremented the count"); } else { // Create a new symbol. The null class loader is never unloaded so these // are allocated specially in a permanent arena. bool c_heap = !loader_data->is_the_null_class_loader_data(); Symbol* sym = allocate_symbol((const u1*)names[i], lengths[i], c_heap, CHECK_(false)); assert(sym->equals(names[i], lengths[i]), "symbol must be properly initialized"); // why wouldn't it be??? HashtableEntry* entry = new_entry(hashValue, sym); add_entry(index, entry); cp->symbol_at_put(cp_indices[i], sym); } } return true;}

通过hash的方式,将字符串添加到常量池中。下一次进行字符串获取时,也就直接从常量池中获取即可。hash作为查找最快的方式,非常有效。因为类信息本身就会反复使用,所以使用常量池或者缓存的方式保存,再好不过。

2.4. 类的查找与初始化

经过常量池处理后,进行实例查找和创建。有点复杂,有可能还涉及到java代码的交互。我们只看大概。

// share/vm/classfile/systemDictionary.cppKlass* SystemDictionary::resolve_or_null(Symbol* class_name, TRAPS) {  return resolve_or_null(class_name, Handle(), Handle(), THREAD);}

// Forwards to resolve_instance_class_or_null
Klass* SystemDictionary::resolve_or_null(Symbol* class_name, Handle class_loader, Handle protection_domain, TRAPS) { assert(!THREAD->is_Compiler_thread(), err_msg("can not load classes with compiler thread: class=%s, classloader=%s", class_name->as_C_string(), class_loader.is_null() ? "null" : class_loader->klass()->name()->as_C_string())); if (FieldType::is_array(class_name)) { return resolve_array_class_or_null(class_name, class_loader, protection_domain, CHECK_NULL); } else if (FieldType::is_obj(class_name)) { ResourceMark rm(THREAD); // Ignore wrapping L and ;. // 类的命名,一定是 Ljava/lang/String; TempNewSymbol name = SymbolTable::new_symbol(class_name->as_C_string() + 1, class_name->utf8_length() - 2, CHECK_NULL); return resolve_instance_class_or_null(name, class_loader, protection_domain, CHECK_NULL); } else { return resolve_instance_class_or_null(class_name, class_loader, protection_domain, CHECK_NULL); }}
Klass* SystemDictionary::resolve_instance_class_or_null(Symbol* name, Handle class_loader, Handle protection_domain, TRAPS) { assert(name != NULL && !FieldType::is_array(name) && !FieldType::is_obj(name), "invalid class name");
Ticks class_load_start_time = Ticks::now();
// UseNewReflection // Fix for 4474172; see evaluation for more details class_loader = Handle(THREAD, java_lang_ClassLoader::non_reflection_class_loader(class_loader())); ClassLoaderData *loader_data = register_loader(class_loader, CHECK_NULL);
// Do lookup to see if class already exist and the protection domain // has the right access // This call uses find which checks protection domain already matches // All subsequent calls use find_class, and set has_loaded_class so that // before we return a result we call out to java to check for valid protection domain // to allow returning the Klass* and add it to the pd_set if it is valid unsigned int d_hash = dictionary()->compute_hash(name, loader_data); int d_index = dictionary()->hash_to_index(d_hash); Klass* probe = dictionary()->find(d_index, d_hash, name, loader_data, protection_domain, THREAD); if (probe != NULL) return probe;

// Non-bootstrap class loaders will call out to class loader and // define via jvm/jni_DefineClass which will acquire the // class loader object lock to protect against multiple threads // defining the class in parallel by accident. // This lock must be acquired here so the waiter will find // any successful result in the SystemDictionary and not attempt // the define // ParallelCapable Classloaders and the bootstrap classloader, // or all classloaders with UnsyncloadClass do not acquire lock here bool DoObjectLock = true; if (is_parallelCapable(class_loader)) { DoObjectLock = false; }
unsigned int p_hash = placeholders()->compute_hash(name, loader_data); int p_index = placeholders()->hash_to_index(p_hash);
// Class is not in SystemDictionary so we have to do loading. // Make sure we are synchronized on the class loader before we proceed Handle lockObject = compute_loader_lock_object(class_loader, THREAD); check_loader_lock_contention(lockObject, THREAD); ObjectLocker ol(lockObject, THREAD, DoObjectLock);
// Check again (after locking) if class already exist in SystemDictionary bool class_has_been_loaded = false; bool super_load_in_progress = false; bool havesupername = false; instanceKlassHandle k; PlaceholderEntry* placeholder; Symbol* superclassname = NULL;
{ MutexLocker mu(SystemDictionary_lock, THREAD); Klass* check = find_class(d_index, d_hash, name, loader_data); if (check != NULL) { // Klass is already loaded, so just return it class_has_been_loaded = true; k = instanceKlassHandle(THREAD, check); } else { placeholder = placeholders()->get_entry(p_index, p_hash, name, loader_data); if (placeholder && placeholder->super_load_in_progress()) { super_load_in_progress = true; if (placeholder->havesupername() == true) { superclassname = placeholder->supername(); havesupername = true; } } } }
// If the class is in the placeholder table, class loading is in progress if (super_load_in_progress && havesupername==true) { k = SystemDictionary::handle_parallel_super_load(name, superclassname, class_loader, protection_domain, lockObject, THREAD); if (HAS_PENDING_EXCEPTION) { return NULL; } if (!k.is_null()) { class_has_been_loaded = true; } }
bool throw_circularity_error = false; if (!class_has_been_loaded) { bool load_instance_added = false;
// add placeholder entry to record loading instance class // Five cases: // All cases need to prevent modifying bootclasssearchpath // in parallel with a classload of same classname // Redefineclasses uses existence of the placeholder for the duration // of the class load to prevent concurrent redefinition of not completely // defined classes. // case 1. traditional classloaders that rely on the classloader object lock // - no other need for LOAD_INSTANCE // case 2. traditional classloaders that break the classloader object lock // as a deadlock workaround. Detection of this case requires that // this check is done while holding the classloader object lock, // and that lock is still held when calling classloader's loadClass. // For these classloaders, we ensure that the first requestor // completes the load and other requestors wait for completion. // case 3. UnsyncloadClass - don't use objectLocker // With this flag, we allow parallel classloading of a // class/classloader pair // case4. Bootstrap classloader - don't own objectLocker // This classloader supports parallelism at the classloader level, // but only allows a single load of a class/classloader pair. // No performance benefit and no deadlock issues. // case 5. parallelCapable user level classloaders - without objectLocker // Allow parallel classloading of a class/classloader pair
{ MutexLocker mu(SystemDictionary_lock, THREAD); if (class_loader.is_null() || !is_parallelCapable(class_loader)) { PlaceholderEntry* oldprobe = placeholders()->get_entry(p_index, p_hash, name, loader_data); if (oldprobe) { // only need check_seen_thread once, not on each loop // 6341374 java/lang/Instrument with -Xcomp if (oldprobe->check_seen_thread(THREAD, PlaceholderTable::LOAD_INSTANCE)) { throw_circularity_error = true; } else { // case 1: traditional: should never see load_in_progress. while (!class_has_been_loaded && oldprobe && oldprobe->instance_load_in_progress()) {
// case 4: bootstrap classloader: prevent futile classloading, // wait on first requestor if (class_loader.is_null()) { SystemDictionary_lock->wait(); } else { // case 2: traditional with broken classloader lock. wait on first // requestor. double_lock_wait(lockObject, THREAD); } // Check if classloading completed while we were waiting Klass* check = find_class(d_index, d_hash, name, loader_data); if (check != NULL) { // Klass is already loaded, so just return it k = instanceKlassHandle(THREAD, check); class_has_been_loaded = true; } // check if other thread failed to load and cleaned up oldprobe = placeholders()->get_entry(p_index, p_hash, name, loader_data); } } } } // All cases: add LOAD_INSTANCE holding SystemDictionary_lock // case 3: UnsyncloadClass || case 5: parallelCapable: allow competing threads to try // LOAD_INSTANCE in parallel
if (!throw_circularity_error && !class_has_been_loaded) { PlaceholderEntry* newprobe = placeholders()->find_and_add(p_index, p_hash, name, loader_data, PlaceholderTable::LOAD_INSTANCE, NULL, THREAD); load_instance_added = true; // For class loaders that do not acquire the classloader object lock, // if they did not catch another thread holding LOAD_INSTANCE, // need a check analogous to the acquire ObjectLocker/find_class // i.e. now that we hold the LOAD_INSTANCE token on loading this class/CL // one final check if the load has already completed // class loaders holding the ObjectLock shouldn't find the class here Klass* check = find_class(d_index, d_hash, name, loader_data); if (check != NULL) { // Klass is already loaded, so return it after checking/adding protection domain k = instanceKlassHandle(THREAD, check); class_has_been_loaded = true; } } }
// must throw error outside of owning lock if (throw_circularity_error) { assert(!HAS_PENDING_EXCEPTION && load_instance_added == false,"circularity error cleanup"); ResourceMark rm(THREAD); THROW_MSG_NULL(vmSymbols::java_lang_ClassCircularityError(), name->as_C_string()); }
if (!class_has_been_loaded) {
// Do actual loading k = load_instance_class(name, class_loader, THREAD);
// For UnsyncloadClass only // If they got a linkageError, check if a parallel class load succeeded. // If it did, then for bytecode resolution the specification requires // that we return the same result we did for the other thread, i.e. the // successfully loaded InstanceKlass // Should not get here for classloaders that support parallelism // with the new cleaner mechanism, even with AllowParallelDefineClass // Bootstrap goes through here to allow for an extra guarantee check if (UnsyncloadClass || (class_loader.is_null())) { if (k.is_null() && HAS_PENDING_EXCEPTION && PENDING_EXCEPTION->is_a(SystemDictionary::LinkageError_klass())) { MutexLocker mu(SystemDictionary_lock, THREAD); Klass* check = find_class(d_index, d_hash, name, loader_data); if (check != NULL) { // Klass is already loaded, so just use it k = instanceKlassHandle(THREAD, check); CLEAR_PENDING_EXCEPTION; guarantee((!class_loader.is_null()), "dup definition for bootstrap loader?"); } } }
// If everything was OK (no exceptions, no null return value), and // class_loader is NOT the defining loader, do a little more bookkeeping. if (!HAS_PENDING_EXCEPTION && !k.is_null() && k->class_loader() != class_loader()) {
check_constraints(d_index, d_hash, k, class_loader, false, THREAD);
// Need to check for a PENDING_EXCEPTION again; check_constraints // can throw and doesn't use the CHECK macro. if (!HAS_PENDING_EXCEPTION) { { // Grabbing the Compile_lock prevents systemDictionary updates // during compilations. MutexLocker mu(Compile_lock, THREAD); update_dictionary(d_index, d_hash, p_index, p_hash, k, class_loader, THREAD); }
if (JvmtiExport::should_post_class_load()) { Thread *thread = THREAD; assert(thread->is_Java_thread(), "thread->is_Java_thread()"); JvmtiExport::post_class_load((JavaThread *) thread, k()); } } } } // load_instance_class loop
if (HAS_PENDING_EXCEPTION) { // An exception, such as OOM could have happened at various places inside // load_instance_class. We might have partially initialized a shared class // and need to clean it up. if (class_loader.is_null()) { // In some cases k may be null. Let's find the shared class again. instanceKlassHandle ik(THREAD, find_shared_class(name)); if (ik.not_null()) { if (ik->class_loader_data() == NULL) { // We didn't go as far as Klass::restore_unshareable_info(), // so nothing to clean up. } else { Klass *kk; { MutexLocker mu(SystemDictionary_lock, THREAD); kk = find_class(d_index, d_hash, name, ik->class_loader_data()); } if (kk != NULL) { // No clean up is needed if the shared class has been entered // into system dictionary, as load_shared_class() won't be called // again. } else { // This must be done outside of the SystemDictionary_lock to // avoid deadlock. // // Note that Klass::restore_unshareable_info (called via // load_instance_class above) is also called outside // of SystemDictionary_lock. Other threads are blocked from // loading this class because they are waiting on the // SystemDictionary_lock until this thread removes // the placeholder below. // // This need to be re-thought when parallel-capable non-boot // classloaders are supported by CDS (today they're not). clean_up_shared_class(ik, class_loader, THREAD); } } } } }
if (load_instance_added == true) { // clean up placeholder entries for LOAD_INSTANCE success or error // This brackets the SystemDictionary updates for both defining // and initiating loaders MutexLocker mu(SystemDictionary_lock, THREAD); placeholders()->find_and_remove(p_index, p_hash, name, loader_data, PlaceholderTable::LOAD_INSTANCE, THREAD); SystemDictionary_lock->notify_all(); } }
if (HAS_PENDING_EXCEPTION || k.is_null()) { return NULL; }
post_class_load_event(class_load_start_time, k, class_loader);
#ifdef ASSERT { ClassLoaderData* loader_data = k->class_loader_data(); MutexLocker mu(SystemDictionary_lock, THREAD); Klass* kk = find_class(name, loader_data); assert(kk == k(), "should be present in dictionary"); }#endif
// return if the protection domain in NULL if (protection_domain() == NULL) return k();
// Check the protection domain has the right access { MutexLocker mu(SystemDictionary_lock, THREAD); // Note that we have an entry, and entries can be deleted only during GC, // so we cannot allow GC to occur while we're holding this entry. // We're using a No_Safepoint_Verifier to catch any place where we // might potentially do a GC at all. // Dictionary::do_unloading() asserts that classes in SD are only // unloaded at a safepoint. Anonymous classes are not in SD. No_Safepoint_Verifier nosafepoint; if (dictionary()->is_valid_protection_domain(d_index, d_hash, name, loader_data, protection_domain)) { return k(); } }
// Verify protection domain. If it fails an exception is thrown validate_protection_domain(k, class_loader, protection_domain, CHECK_NULL);
return k();}

有点复杂,空了细看吧。另外可以提一下的就是,每一次class的加载,都会附带一个锁的操作

{ MutexLocker mu(SystemDictionary_lock, THREAD);  kk = find_class(d_index, d_hash, name, ik->class_loader_data()); }


这种锁超出作用域后,就会调用析构方法,然后就会自动进行锁释放。这和很多的锁需要 lock() -> unlock() 到是省了一些事。

2.5. 类实例的返回

JNIHandles::make_local(), 大概意思是将前面解析出来的 Klass 转换对应的 jclass , 而这其中又有很多弯弯绕。

// share/vm/runtime/jniHandles.cppjobject JNIHandles::make_local(JNIEnv* env, oop obj) {  if (obj == NULL) {    return NULL;                // ignore null handles  } else {    JavaThread* thread = JavaThread::thread_from_jni_environment(env);    assert(Universe::heap()->is_in_reserved(obj), "sanity check");    return thread->active_handles()->allocate_handle(obj);  }}
jobject JNIHandleBlock::allocate_handle(oop obj) { assert(Universe::heap()->is_in_reserved(obj), "sanity check"); if (_top == 0) { // This is the first allocation or the initial block got zapped when // entering a native function. If we have any following blocks they are // not valid anymore. for (JNIHandleBlock* current = _next; current != NULL; current = current->_next) { assert(current->_last == NULL, "only first block should have _last set"); assert(current->_free_list == NULL, "only first block should have _free_list set"); current->_top = 0; if (ZapJNIHandleArea) current->zap(); } // Clear initial block _free_list = NULL; _allocate_before_rebuild = 0; _last = this; if (ZapJNIHandleArea) zap(); }
// Try last block if (_last->_top < block_size_in_oops) { oop* handle = &(_last->_handles)[_last->_top++]; *handle = obj; // 出口1 return (jobject) handle; }
// Try free list if (_free_list != NULL) { oop* handle = _free_list; _free_list = (oop*) *_free_list; *handle = obj; // 出口2 return (jobject) handle; } // Check if unused block follow last if (_last->_next != NULL) { // update last and retry _last = _last->_next; return allocate_handle(obj); }
// No space available, we have to rebuild free list or expand if (_allocate_before_rebuild == 0) { rebuild_free_list(); // updates _allocate_before_rebuild counter } else { // Append new block Thread* thread = Thread::current(); Handle obj_handle(thread, obj); // This can block, so we need to preserve obj accross call. _last->_next = JNIHandleBlock::allocate_block(thread); _last = _last->_next; _allocate_before_rebuild--; obj = obj_handle(); } return allocate_handle(obj); // retry}

主要就是一个类型的转换,或者包装Kclass 以便可以操作更多,细节自行阅读。

3. 一点闲话

本文着重讲解了jvm对java类的查找,以及对类方法的查找实现。而且看起来,实现得挺复杂挺难的样子。

然而,我们单就对一个类的查找方法的查找而言,应该是很简单的。而且两场景相似度也很高,只是一个入参是类名,另一个是方法签名。比如,对类的查找,无外乎一个hash数据结构的存取实现而已。只是在对类的初始过程,需要保证线程安全而已。而对于方法的查找,则可能更简单,因为方法毕竟有限,不如类来得多。甚至可能就是一个链表搞定,通过遍历签名即可得到方法id。

实际上,当我们提出一个问题时,往往就已经将事情简单化了,或许已关系场景本身的初衷。因为,像jvm这种高难度玩意,需要极高的理论基础,设计能力,极广的知识面,以及超高的实现能力。因为,它本身的场景,就是提供各种不确定性。我等,只是做个吃瓜群众罢了。

不过幸好,至少我们可以验证一些猜想,没有虚妄。至少会以为,抽丝剥茧之后的,我们都会。




往期精彩推荐



腾讯、阿里、滴滴后台面试题汇总总结 — (含答案)

面试:史上最全多线程面试题 !

最新阿里内推Java后端面试题

JVM难学?那是因为你没认真看完这篇文章


END


关注作者微信公众号 —《JAVA烂猪皮》


了解更多java后端架构知识以及最新面试宝典


你点的每个好看,我都认真当成了


看完本文记得给作者点赞+在看哦~~~大家的支持,是作者源源不断出文的动力


作者:等你归去来

出处:https://www.cnblogs.com/yougewe/p/14426936.html

浏览 28
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报