JVM系列(四):java方法的查找过程实现
经过前面几章的简单介绍,我们已经大致了解了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 都定义了啥。这对于我们以后的分析工作有非常大的帮助。
// 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
};
以上就是 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 以便可以操作更多,细节自行阅读。
JVM系列(四):java方法的查找过程实现的更多相关文章
- jvm系列(四):jvm知识点总结
原文链接:http://www.cnblogs.com/ityouknow/p/6482464.html jvm 总体梳理 jvm体系总体分四大块: 类的加载机制 jvm内存结构 GC算法 垃圾回收 ...
- jvm系列四、jvm知识点总结
原文链接:http://www.cnblogs.com/ityouknow/p/6482464.html jvm 总体梳理 jvm体系总体分四大块: 类的加载机制 jvm内存结构 GC算法 垃圾回收 ...
- jvm系列(九):Java GC 分析
Java GC就是JVM记录仪,书画了JVM各个分区的表演. 什么是 Java GC Java GC(Garbage Collection,垃圾收集,垃圾回收)机制,是Java与C++/C的主要区别之 ...
- jvm系列(五):Java GC 分析
Java GC就是JVM记录仪,书画了JVM各个分区的表演. 什么是 Java GC Java GC(Garbage Collection,垃圾收集,垃圾回收)机制,是Java与C++/C的主要区别之 ...
- jvm系列(一):java类的加载机制
java类的加载机制 1.什么是类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装 ...
- jvm系列 (四) ---强、软、弱、虚引用
java引用 目录 jvm系列(一):jvm内存区域与溢出 jvm系列(二):垃圾收集器与内存分配策略 jvm系列(三):锁的优化 我的博客目录 为什么将引用分为不同的强度 因为我们需要实现这样一种情 ...
- jvm系列(六):Java服务GC参数调优案例
本文介绍了一次生产环境的JVM GC相关参数的调优过程,通过参数的调整避免了GC卡顿对JAVA服务成功率的影响. 这段时间在整理jvm系列的文章,无意中发现本文,作者思路清晰通过步步分析最终解决问题. ...
- Java设计模式菜鸟系列(四)工厂方法模式建模与实现
转载请注明出处:http://blog.csdn.net/lhy_ycu/article/details/39760895 工厂方法模式(Factory Method) 工厂方法:顾名思义,就是调用工 ...
- jvm系列五-java内存模型(2)
原作者系列文章链接:并发编程系列博客传送门 前言# 在网上看了很多文章,也看了好几本书中关于JMM的介绍,我发现JMM确实是Java中比较难以理解的概念.网上很多文章中关于JMM的介绍要么是照搬了一些 ...
随机推荐
- C链表-C语言入门经典例题
struct student { long num; float score; struct student *next; }; 注意:只是定义了一个struct student类型,并未实际分配存储 ...
- Java 复习整理day05
1 package com.it.demo01_oop; 2 3 import java.util.Arrays; 4 5 /* 6 案例: 演示面向过程和面向对象代码的区别 7 8 面向过程编程思想 ...
- 跟着Vimtutor学习Vim
跟着Vimtutor学习Vim Lesson 1 1.1 移动光标 在Vim中移动光标,分别使用h.j.k.l键代表左.下.上.右方向. 1.2 退出VIM :q! <ENTER> 退出V ...
- CentOS7.X安装英伟达显卡采坑之路
1.系统信息 操作系统版本:CentOS7.X 显卡版本:英伟达 Tesla P100 其他软件包安装信息: CUDA 9.0 CUDNN 7.4.2.24 lightgbm 2.2.X Boost ...
- 封装打包Python脚本
1.前言 封装打包Python的好处,节省了安装各种各样包依赖的问题,同时可以加强我们代码隐私的安全性,这里我的演示环境是Python3.6 ,CentOS7的系统,同时打包工具采用pyinstall ...
- 图片轮播展示效果-2D实现
图片的轮播展示效果如果使用2D实现,需要将3D中存在的近大远小效果使用图片的缩放呈现,因此需要存储和计算图片的位置同时还要计算存储图片的缩放信息.将所有图片的位置连线看作是一个椭圆,就可以根据图片的个 ...
- Java初体验
参考书籍「Java语言程序设计基础篇」 比特与字节 计算机中只有0和1,二进制,即比特(bit,二进制数): 字节(byte)是最小的存储单元,每个字节有8个比特组成 即:1byte=8bit 各种数 ...
- HDU - 4300 Clairewd’s message (拓展kmp)
HDU - 4300 题意:这个题目好难读懂,,先给你一个字母的转换表,然后给你一个字符串密文+明文,密文一定是全的,但明文不一定是全的,求最短的密文和解密后的明文: 题解:由于密文一定是全的,所以他 ...
- hdu3247Resource Archiver (AC自动机+最短路+状压dp)
Time Limit: 20000/10000 MS (Java/Others) Memory Limit: 100000/100000 K (Java/Others) Total Submis ...
- python实现通过指定浏览器免费观看vip视频
程序是先通过一个解析视频的网站,然后我们提取其接口,然后实现观看vip视频的目的 所以说免费观看视频python程序很容易,但是下载视频就有些许麻烦了,下载视频请见我另一篇博客:python+fidd ...