JVM系列(四):java方法的查詢過程實現

等你歸去來發表於2021-02-21

  經過前面幾章的簡單介紹,我們已經大致瞭解了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.cpp
JavaThread::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 thread
void 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<oop&>(_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 structure
struct 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 都定義了啥。這對於我們以後的分析工作有非常大的幫助。

JVM系列(四):java方法的查詢過程實現
// jni.cpp
// Structure containing all jni functions
struct 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
};
View Code

  以上就是 JNIEnv* env 變數的設值過程了,它藉助於java執行緒的建立時機進行初始化。而後續的使用中,幾乎都會仰仗它來執行,可見其重要性。

  但總結一下,這裡面提供的介面,實際上都是一些非常基礎的操作,比如變數新建,初始化,異常處理,鎖處理,native註冊等。型別實際並不多。這也提示了我們一點,越是基礎的東西,實際上越不會那麼複雜。它更多的是做好抽象工作,打好基礎,比什麼都好。

 

2. main方法的查詢實現

  要談其他方法,著實也太泛了。因為,你可以定義這個方法,他可以定義一個別的方法。這裡面的特性就太難找了。但,對於每個java應用的啟動,都會去載入main()方法執行,所以,以這個main()方法的查詢為出發點,定然能找到些端倪來。

  我們先來看看main()的呼叫地方如何:

// share/bin/java.c
// 載入 main 函式類
// 通過引入 JavaMain(), 接入java方法
// #define JNICALL __stdcall
int JNICALL
JavaMain(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.cpp
JNI_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 根據方法簽名,找到方法id
static 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"
  // "<init>" "<clinit>"
  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 jclass
LoadMainClass(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;
}    
jclass
GetLauncherHelperClass(JNIEnv *env)
{
    if (helperClass == NULL) {
        // 查詢 helplerClass, 並快取
        NULL_CHECK0(helperClass = FindBootStrapClass(env,
                "sun/launcher/LauncherHelper"));
    }
    return helperClass;
}
// solaris/bin/java_md_common.c
// 查詢啟動類
jclass
FindBootStrapClass(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<names_count; 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<Symbol*, mtSymbol>* 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.cpp
Klass* 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.cpp
jobject 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這種高難度玩意,需要極高的理論基礎,設計能力,極廣的知識面,以及超高的實現能力。因為,它本身的場景,就是提供各種不確定性。我等,只是做個吃瓜群眾罷了。

相關文章