经过前面几章的简单介绍,我们已经大致了解了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 threadJNIEnv* 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);}// 初始化线程变量信息, 如 JNIEnvinitialize();_jni_attach_state = _not_attaching_via_jni;set_entry_point(entry_point);// Create the native thread itself.// %note runtime_23os::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_excepad0tion_pc(NULL);set_threadObj(NULL);_anchor.clear();set_entry_point(NULL);// 取数jni_functions, 初始化到 _jni_environmentset_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_NMTset_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 threadThreadSafepointState::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_CHECKif (CheckJNICalls) return jni_functions_check();#endif // INCLUDE_JNI_CHECKreturn &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,25ecjni_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 featuresjni_GetObjectRefType};
View Code
以上就是 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 launchedjmethodID mainID;jobjectArray mainArgs;int ret = 0;jlong start, end;// collectorRegisterThread();/* Initialize the virtual machine */start = CounterGet();// 重点1:初1044始化jvm,失败则退出// 此处会将重要变量 *env 进程初始化,从而使后续可用if (!InitializeJVM(&vm, &env, &ifn)) {JLI_ReportErrorMessage(JVM_ERROR1);exit(1);}// jvm检查完毕,如果只是一些展示类请求,则展示信息后,退出jvmif (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 USDT2DTRACE_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 USDT2DTRACE_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 USDT2DTRACE_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 USDT2DTRACE_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.Classif (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 constructorsif (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);}// 返回idreturn m->jmethod_id();}// share/vm/oops/klass.cpppublic: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 ASSERTtty->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.\");#endifShouldNotReachHere();return NULL564;}// oops/method.hpp// Get this method\'s jmethodID -- allocate if it doesn\'t existjmethodID 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.// Cach56ce 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 existsif (!ik_h->idnum_can_increment()) {// the cache can\'t grow so we can just get the current valuesget_jmethod_id_length_value(jmeths, idnum, &length, &id);} else {// cache can grow so we have to be more carefulif (Threads::number_of_threads() ==ad80 ||SafepointSynchronize::is_at_safepoint()) {// we\'re single threaded or at a safepoint - no locking neededget_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 goodif (jmeths == NULL || // no cache yetlength <= idnum || // cache is too shortid == 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 itjmethodID* new_jmeths = NULL;if (length <= idnum) {// allocate a new cache that might be usedsize_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 onenew_jmeths[0] = (jmethodID)size;}// allocate a new jmethodID that might be usedjmethodID 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 versionMethod* 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 innew_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 neededid = 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 neededif (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/LauncherHelperjclass 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...// 类名称最长不超过65535if (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 creationstatic 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);// Foundif (s != NULL) return s;// 上锁添加常量池// Grab SymbolTable_lock first.MutexLocker ml(SymbolTable_lock, THREAD);// Otherwise, add to symbol to tablereturn the_table()->basic_add(index, (u1*)name, len, hashValue, true, CHECK_NULL);}// This version of basic_add add56cs 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->15a8symbol_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.cppKlass* SystemDictionary::resolve_or_null(Symbol* class_name, TRAPS) {return resolve_or_null(class_name, Handle(), Handle(), THREAD);}// Forwards to resolve_instance_class_or_nullKlass* 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 detailsclass_loader = Handle(THREAD, java_lang_ClassLoader::non_reflection_class_loader(class_loader()));ClassLoaderData *loader_data = register_loader(class_loader, CHECK_Nad8ULL);// 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 validunsigned 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 herebool 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 proceedHandle 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 SystemDictionarybool 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 itclass_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 progressif (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()->ge1b1ct_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 -Xcompif (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 requestorif (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 waitingKlass* check = find_class(d_index, d_hash, name, loader_data);if (check != NULL) {// Klass is already loaded, so just return itk = instanceKlassHandle(THREAD, check);class_has_been_loaded = true;}// check if other thread failed to load and cleaned upoldprobe = 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 parallelif (!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 hereKlass* check = find_class(d_index, d_hash, name, loader_data);if (check != NULL) {// Klass is already loaded, so return it after checking/adding protection domaink = instanceKlassHandle(THREAD, check);class_has_been_loaded = true;}}}// must throw error outside of owning lockif (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 loadingk = 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 checkif (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 itk = 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) {ad8{ // 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 loopif (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 loadersMutexLocker 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 NULLif (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 thrownvalidate_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 blockif (_last->_top < block_size_in_oops) {oop* handle = &(_last->_handles)[_last->_top++];*handle = obj;// 出口1return (jobject) handle;}// Try free listif (_free_list != NULL) {oop* handle = _free_list;_free_list = (oop*) *_free_list;*handle = obj;// 出口2return (jobject) handle;}// Check if unused block follow lastif (_last->_next != NULL) {// update last and retry_last = _last->_next;return allocate_handle(obj);}// No space available, we have to rebuild free list or expandif (_allocate_before_rebuild == 0) {rebuild_free_list(); // updates _allocate_before_rebuild counter} else {// Append new blockThread* 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这种高难度玩意,需要极高的理论基础,设计能力,极广的知识面,以及超高的实现能力。因为,它本身的场景,就是提供各种不确定性。我等,只是做个吃瓜群众罢了。