在第1篇中大概介紹過Java中主類方法main()的調用過程,這一篇介紹的詳細一點,大概的調用過程如下圖所示。

其中淺紅色的函數由主線程執行,而另外的淺綠色部分由另外一個線程執行,這個線程最終也會負責執行Java主類中的main()方法。在JavaMain()函數中調用LoadMainClass()函數加載Java主類。接着在JavaMain()函數中有如下調用:
源代碼位置:openjdk/jdk/src/share/bin/java.c mainID = (*env)->GetStaticMethodID(env, mainClass, "main", "([Ljava/lang/String;)V");
env為JNIEnv*類型。調用JNIEnv類型中定義的GetStaticMethodID()函數獲取Java主類中main()方法的方法唯一ID,調用GetStaticMethodID()函數就是調用到jni_GetStaticMethodID()函數,實現如下:
源代碼位置:openjdk/hotspot/src/share/vm/prims/jni.cpp
JNI_ENTRY(jmethodID, jni_GetStaticMethodID(JNIEnv *env, jclass clazz,const char *name, const char *sig))
jmethodID ret = get_method_id(env, clazz, name, sig, true, thread);
return ret;
JNI_END
static jmethodID get_method_id(
JNIEnv *env,
jclass clazz,
const char *name_str,
const char *sig,
bool is_static,
TRAPS
){
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));
TempNewSymbol signature = SymbolTable::probe(sig, (int)strlen(sig));
KlassHandle klass(THREAD,java_lang_Class::as_Klass(JNIHandles::resolve_non_null(clazz)));
// 保證java.lang.Class類已經初始化完成
klass()->initialize(CHECK_NULL);
Method* m;
if ( name == vmSymbols::object_initializer_name() || 查找的是<init>方法
name == vmSymbols::class_initializer_name() ) { 查找的是<clinit>方法
// 因為要查找的是構造函數,構造函數沒有繼承特性,所以當前類找不到時不向父類中繼續查找
if (klass->oop_is_instance()) {
// find_method()函數不會向上查找
m = InstanceKlass::cast(klass())->find_method(name, signature);
} else {
m = NULL;
}
} else {
// lookup_method()函數會向上查找
m = klass->lookup_method(name, signature);
if (m == NULL && klass->oop_is_instance()) {
m = InstanceKlass::cast(klass())->lookup_method_in_ordered_interfaces(name, signature);
}
}
return m->jmethod_id();
}
獲取Java類中main()方法的jmethod_id。
源代碼位置: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);
}
調用的InstanceKlass::get_jmethod_id()函數獲取唯一ID,關於如何獲取或生成ID的過程這里不再詳細介紹,有興趣的自行研究。
在JavaMain()函數中有如下調用:
mainArgs = CreateApplicationArgs(env, argv, argc); (*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);
通過調用CallStaticVoidMethod()函數來調用Java主類中的main()方法。控制權轉移到JavaMainClass.main()方法之中,等JavaMainClass.main()方法返回之后當前線程才接手過來清理和關閉HotSpot VM。
源代碼位置:openjdk/hotspot/src/share/vm/prims/jni.cpp JNI_ENTRY(void, jni_CallStaticVoidMethod(JNIEnv *env, jclass cls, jmethodID methodID, ...)) va_list args; va_start(args, methodID); JavaValue jvalue(T_VOID); JNI_ArgumentPusherVaArg ap(methodID, args); jni_invoke_static(env, &jvalue, NULL, JNI_STATIC, methodID, &ap, CHECK); va_end(args); JNI_END
va_list與va_start是變長參數解析時需要用到的宏。將傳給Java方法的參數以C的可變長度參數傳入后,使用JNI_ArgumentPusherVaArg實例ap是將其封裝起來。JNI_ArgumentPusherVaArg類的繼承體系如下:
JNI_ArgumentPusherVaArg->JNI_ArgumentPusher->SignatureIterator
調用的jni_invoke_static()函數的實現如下:
// 通過jni的方式調用Java靜態方法
static void jni_invoke_static(
JNIEnv *env,
JavaValue* result,
jobject receiver,
JNICallType call_type,
jmethodID method_id,
JNI_ArgumentPusher *args,
TRAPS
){
Method* m = Method::resolve_jmethod_id(method_id);
methodHandle method(THREAD, m);
ResourceMark rm(THREAD);
int number_of_parameters = method->size_of_parameters();
// 這里進一步將要傳給Java的參數轉換為JavaCallArguments對象傳下去
JavaCallArguments java_args(number_of_parameters);
args->set_java_argument_object(&java_args);
// Fill out(填,填寫) JavaCallArguments object
Fingerprinter fp = Fingerprinter(method);
uint64_t x = fp.fingerprint();
args->iterate(x);
// Initialize result type
BasicType bt = args->get_ret_type();
result->set_type(bt);
// Invoke the method. Result is returned as oop.
JavaCalls::call(result, method, &java_args, CHECK);
// Convert result
if (
result->get_type() == T_OBJECT ||
result->get_type() == T_ARRAY
) {
oop tmp = (oop) result->get_jobject();
jobject jobj = JNIHandles::make_local(env,tmp);
result->set_jobject(jobj);
}
}
通過JavaCalls::call()函數來調用Java主類的main()方法。關於JavaCalls::call()函數大家應該不會陌生,這個函數是怎么建立Java棧幀以及找到Java方法入口在之前詳細介紹過。
推薦閱讀:
第2篇-JVM虛擬機這樣來調用Java主類的main()方法
第13篇-通過InterpreterCodelet存儲機器指令片段
第20篇-加載與存儲指令之ldc與_fast_aldc指令(2)
第21篇-加載與存儲指令之iload、_fast_iload等(3)

