在JavaMain()函數(定義在openjdk/jdk/src/share/bin/java.c文件中)中調用LoadMainClass()函數加載Java主類。LoadMainClass()函數的實現如下:
源代碼位置:openjdk/jdk/src/share/bin/java.c
/*
* 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);
// 獲取sun.launcher.LauncherHelper類中定義的checkAndLoadMain()方法的指針
NULL_CHECK0(mid = (*env)->GetStaticMethodID(env,cls,"checkAndLoadMain","(ZILjava/lang/String;)Ljava/lang/Class;"));
// 調用sun.launcher.LauncherHelper類中的checkAndLoadMain()方法
str = NewPlatformString(env, name);
result = (*env)->CallStaticObjectMethod(env, cls, mid, USE_STDERR, mode, str);
return (jclass)result;
}
下面介紹如上函數調用的一些函數。
1、GetLauncherHelperClass()函數
調用的GetLauncherHelperClass()函數的實現如下:
jclass GetLauncherHelperClass(JNIEnv *env){
if (helperClass == NULL) {
NULL_CHECK0(helperClass = FindBootStrapClass(env,"sun/launcher/LauncherHelper"));
}
return helperClass;
}
/*
* The implementation for finding classes from the bootstrap
* class loader, refer to java.h
*/
static FindClassFromBootLoader_t *findBootClass = NULL;
// 參數classname的值為"sun/launcher/LauncherHelper"。
jclass FindBootStrapClass(JNIEnv *env, const char* classname){
if (findBootClass == NULL) {
// 返回指向JVM_FindClassFromBootLoader()函數的函數指針
findBootClass = (FindClassFromBootLoader_t *)dlsym(RTLD_DEFAULT,"JVM_FindClassFromBootLoader");
}
return findBootClass(env, classname);
}
通過函數指針findBootClass來調用JVM_FindClassFromBootLoader()函數。JVM_FindClassFromBootLoader()函數的實現如下:
JVM_ENTRY(jclass, JVM_FindClassFromBootLoader(JNIEnv* env,const char* name))
TempNewSymbol h_name = SymbolTable::new_symbol(name, CHECK_NULL);
Klass* k = SystemDictionary::resolve_or_null(h_name, CHECK_NULL);
if (k == NULL) {
return NULL;
}
return (jclass) JNIHandles::make_local(env, k->java_mirror());
JVM_END
調用的SystemDictionary::resolve_or_null()函數在前面已經詳細介紹過,這里不再介紹。
2、GetStaticMethodID()函數
在通過JNI的方式調用Java方法時,首先要獲取到方法的methodID。調用GetStaticMethodID()函數查找Java啟動方法(Java主類中的main()方法)的methodID。調用GetStaticMethodID()函數其實調用的是jni_GetStaticMethodID()函數,實現如下:
// 傳遞的參數name為"checkAndLoadMain",而sig為"(ZILjava/lang/String;)Ljava/lang/Class;"。 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
get_method_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));
TempNewSymbol signature = SymbolTable::probe(sig, (int)strlen(sig));
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;
if (name == vmSymbols::object_initializer_name() || // name為<init>
name == vmSymbols::class_initializer_name()) { // name為<clinit>
// Never search superclasses for constructors
if (klass->oop_is_instance()) { // 在查找構造函數時,只查找當前類中的構造函數,不查找超類構造函數
m = InstanceKlass::cast(klass())->find_method(name, signature);
} else {
m = NULL;
}
} else {
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(); // 獲取方法對應的methodID,methodID指定后不會變,所以可以重復使用methodID
}
查找構造函數時調用InstanceKlass類中的find_method()方法,這個方法不會查找超類;查找普通方法時,調用Klass中的lookup_method()或InstanceKlass類中的lookup_method_in_ordered_interfaces()方法,這兩個方法會從父類中查找,例如lookup_method()方法的實現如下:
Method* lookup_method(Symbol* name, Symbol* signature) const {
return uncached_lookup_method(name, signature);
}
// uncached_lookup_method searches both the local class methods array and all
// superclasses methods arrays, skipping any overpass methods in superclasses.
Method* InstanceKlass::uncached_lookup_method(Symbol* name, Symbol* signature) const {
Klass* klass = const_cast<InstanceKlass*>(this);
bool dont_ignore_overpasses = true; // For the class being searched, find its overpasses.
while (klass != NULL) {
Method* method = InstanceKlass::cast(klass)->find_method(name, signature);
if ((method != NULL) && (dont_ignore_overpasses || !method->is_overpass())) {
return method;
}
klass = InstanceKlass::cast(klass)->super();
dont_ignore_overpasses = false; // Ignore overpass methods in all superclasses.
}
return NULL;
}
如果調用find_method()無法從當前類中查找到對應的方法,那么通過while循環一直從繼承鏈往上查找,如果找到就直接返回,否則返回NULL。
find_method()方法的實現如下:
// find_method looks up the name/signature in the local methods array
Method* InstanceKlass::find_method(Symbol* name, Symbol* signature) const {
return InstanceKlass::find_method(methods(), name, signature);
}
// find_method looks up the name/signature in the local methods array
Method* InstanceKlass::find_method(Array<Method*>* methods, Symbol* name, Symbol* signature) {
int hit = find_method_index(methods, name, signature);
return hit >= 0 ? methods->at(hit): NULL;
}
// Used directly for default_methods to find the index into the
// default_vtable_indices, and indirectly by find_method
// find_method_index looks in the local methods array to return the index
// of the matching name/signature
int InstanceKlass::find_method_index(Array<Method*>* methods, Symbol* name, Symbol* signature) {
int hit = binary_search(methods, name); // 從methods中通過二分算法來查找名稱為name的方法
if (hit != -1) {
Method* m = methods->at(hit);
// Do linear search to find matching signature. First, quick check for common case
if (m->signature() == signature)
return hit;
// search downwards through overloaded methods
int i;
for (i = hit - 1; i >= 0; --i) {
Method* m = methods->at(i);
if (m->name() != name)
break;
if (m->signature() == signature)
return i;
}
// search upwards
for (i = hit + 1; i < methods->length(); ++i) {
Method* m = methods->at(i);
if (m->name() != name)
break;
if (m->signature() == signature)
return i;
}
// not found
}
return -1;
}
當前的方法存儲在instanceKlass類的_methods屬性中,並且是按一定的順序存儲,這樣就可以使用二分查找算法加快查找速度了,如果找到方法,則返回對應在數組中的下標位置,否則返回-1。
3、CallStaticObjectMethod()函數
在LoadMainClass()函數中調用(*env)->CallStaticObjectMethod()函數會執行sun.launcher.LauncherHelper類的checkAndLoadMain()方法。CallStaticObjectMethod()方法定義在jni.cpp文件中,實現時會通過jni_invoke_static()函數執行checkAndLoadMain()方法。jni_invoke_static()函數的實現如下:
static void jni_invoke_static(JNIEnv *env, JavaValue* result, jobject receiver, JNICallType call_type,
jmethodID method_id, JNI_ArgumentPusher *args, TRAPS) {
methodHandle method(THREAD, Method::resolve_jmethod_id(method_id));
// Create object to hold arguments for the JavaCall, and associate it with
// the jni parser
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);
assert(method->is_static(), "method should be static");
// Fill out JavaCallArguments object
args->iterate( Fingerprinter(method).fingerprint() );
// Initialize result type
result->set_type(args->get_ret_type());
// Invoke the method. Result is returned as oop.
// 供C/C++程序調用Java方法
JavaCalls::call(result, method, &java_args, CHECK);
// Convert result
if (result->get_type() == T_OBJECT || result->get_type() == T_ARRAY) {
result->set_jobject(JNIHandles::make_local(env, (oop) result->get_jobject()));
}
}
函數最終通過JavaCalls::call()方法調用Java方法,在介紹方法執行引擎時將會詳細介紹。方法看起來邏輯很多,其實都是因為JNI調用時,需要對參數進行轉換,在JNI環境下只能使用句柄來訪問虛擬機對象,而在虛擬機操作時,由於實現邏輯的需要,必須要操作虛擬機對象,所以不可避免在每次方法的開始和結束都需要對參數進行轉換。如調用Method::resolve_jmethod_id()、調用JNIHandles::make_local()等方法。
最后看一下調用JavaCalls::call()方法執行的Java方法checkAndLoadMain()方法的實現,如下:
源代碼位置如下:/home/mazhi/workspace/openjdk/jdk/src/share/classes/sun/launcher/LauncherHelper.java
/**
* This method does the following:
* 1. gets the classname from a Jar's manifest, if necessary
* 2. loads the class using the System ClassLoader
* 3. ensures the availability and accessibility of the main method,
* using signatureDiagnostic method.
* a. does the class exist
* b. is there a main
* c. is the main public
* d. is the main static
* e. does the main take a String array for args
* 4. if no main method and if the class extends FX Application, then call
* on FXHelper to determine the main class to launch
* 5. and off we go......
*
* @param printToStderr if set, all output will be routed to stderr
* @param mode LaunchMode as determined by the arguments passed on the
* command line
* @param what either the jar file to launch or the main class when using
* LM_CLASS mode
* @return the application's main class
*/
public static Class<?> checkAndLoadMain(boolean printToStderr,
int mode,
String what) {
initOutput(printToStderr);
// get the class name
String cn = null;
switch (mode) {
case LM_CLASS:
cn = what;
break;
case LM_JAR:
cn = getMainClassFromJar(what);
break;
default:
// should never happen
throw new InternalError("" + mode + ": Unknown launch mode");
}
cn = cn.replace('/', '.');
Class<?> mainClass = null;
try {
mainClass = scloader.loadClass(cn); // 加載主類
} catch (NoClassDefFoundError | ClassNotFoundException cnfe) {
...
}
// set to mainClass
appClass = mainClass;
return mainClass;
}
從這里可以看出加載main方法類的加載器是系統類加載器,而系統類加載器其實就是AppClassLoader。所以,main方法默認加載器是AppClassLoder,並且傳給當前線程上下文的加載器也是AppClassLoader。
AppClassLoader/ExtClassLoader都是Launcher的內部類。先初始化ExtClassLoader,並將ExtClassLoader作為父加載器傳給AppClassLoder
private static final ClassLoader scloader = ClassLoader.getSystemClassLoader();
調用scloader的loadClass()方法會調用到java.lang.ClassLoader的loadClass()方法,之前已經介紹過這個方法,首先通過findLoadedClass()方法判斷當前加載器是否已經加載了指定的類,如果沒有加載並且parent不為NULL,調用parent.loadClass()方法來完成,而AppClassLoader的父加載器是ExtClassLoader,這是加載JDK中的擴展類,並不會加載Java主類,所以只能調用this.findClass()方法來完成主類的加載。對於AppClassLoader來說,調用的是URLClassLoader中實現的findClass()方法,最終會調用本地方法defineClass1()來完成,這個方法在介紹類的雙親委派機制時詳細介紹過,這里不再介紹。
相關文章的鏈接如下:
1、在Ubuntu 16.04上編譯OpenJDK8的源代碼
13、類加載器
14、類的雙親委派機制
15、核心類的預裝載
作者持續維護的個人博客classloading.com。
關注公眾號,有HotSpot源碼剖析系列文章!
