經過上一節我們知道最終main方法中會通過pthread_create創建一個新的線程來執行JavaMain方法,下面我們慢慢來剖析JavaMain核心流程,在此列出來的代碼我們也只列出來核心代碼,後續就不再做此說明
JavaMain
java.c 文件中JavaMain方法中主要初始化Jvm虛擬機,加載Java程序需要使用到的mainClass, 找到main方法,執行main,這樣我們Java main就成功的被調用了
int JNICALL JavaMain(void * _args)
{
/* Initialize the virtual machine */
if (!InitializeJVM(&vm, &env, &ifn)) {
JLI_ReportErrorMessage(JVM_ERROR1);
exit(1);
}
//Get the application's main class. It also checks if the main method exists.
jclass mainClass = LoadMainClass(env, mode, what);
/*
* 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.
*/
jmethodID mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
"([Ljava/lang/String;)V");
/* Invoke main method. */
(*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);
}
執行流程說明:
- InitializeJVM初始化Jvm,這個方法內部邏輯比較複雜先不要去挖,看完主流程,它的功能就是初始化Java虛擬機,包含如下模塊,內存管理,GC,類加載模塊等
- LoadMainClass ,獲取應用程序的Main Class,也就是帶main方法class ,jclass聲明下面列出來了,是一個空無成員變量的class指針,由此可知這個指針肯定就是指向了一個class在內存中的地址
class _jobject {};
class _jclass : public _jobject {};
typedef _jclass *jclass;
- GetStaticMethodID ,通過這個能獲取到mainClass的main方法在內存中的地址,返回值爲jmethodID 指向一個空結構體的指針,這個指針是指向了方法體在內存中的地址
struct _jmethodID;
typedef struct _jmethodID *jmethodID;
- CallStaticVoidMethod ,調用查找到的main方法,這樣就進入了我們使用java寫的main方法了,程序開始執行我們寫Java main方法了,在此我們注意到最後兩方法都是調用JNIEnv *env的方法,此結構定義在Jni.h文件中 ,此接口定義了很多函數指向,而這些是在InitializeJVM方法進行賦值,我們看下定義;再往下看看,此結構體是怎麼賦值,裏面的這些方法在什麼文件中定義,方便後面深入具體方法
typedef const struct JNINativeInterface_ *JNIEnv;
struct JNINativeInterface_ {
jmethodID (JNICALL *GetStaticMethodID)(JNIEnv *env, jclass clazz, const char *name, const char *sig);
jmethodID (JNICALL *GetMethodID) (JNIEnv *env, jclass clazz, const char *name, const char *sig);
jobject (JNICALL *CallStaticObjectMethod)(JNIEnv *env, jclass clazz, jmethodID methodID, ...);
}
InitializeJVM
InitializeJVM 將JavaVM **pvm, JNIEnv **penv繼續往傳遞調用ifn->CreateJavaVM,上節我們知道ifn->CreateJavaVM就是指向JNI_CreateJavaVM函數
/*
* Initializes the Java Virtual Machine. Also frees options array when finished.
*/
static jboolean InitializeJVM(JavaVM **pvm, JNIEnv **penv, InvocationFunctions *ifn)
{
JavaVMInitArgs args;
jint r;
memset(&args, 0, sizeof(args));
args.version = JNI_VERSION_1_2;
args.nOptions = numOptions;
args.options = options;
args.ignoreUnrecognized = JNI_FALSE;
r = ifn->CreateJavaVM(pvm, (void **)penv, &args);
return r == JNI_OK;
}
###JNI_CreateJavaVM
在jni.cpp中,會直接調用JNI_CreateJavaVM_inner(JavaVM **vm, void **penv, void *args),此方法有些長我們暫時先關注JNIEnv如何賦值,後續在解密其他方法
static jint JNI_CreateJavaVM_inner(JavaVM **vm, void **penv, void *args) {
result = Threads::create_vm((JavaVMInitArgs*) args, &can_try_again);
if (result == JNI_OK) {
JavaThread *thread = JavaThread::current();
assert(!thread->has_pending_exception(), "should have returned not OK");
/* thread is thread_in_vm here */
*vm = (JavaVM *)(&main_vm);
*(JNIEnv**)penv = thread->jni_environment();
}
}
由如下代碼可知JNIEnv* penv = thread->jni_environment(),那麼我們從這可以知道了這個JNIEnv是當前創建的線程的一個成員變量_jni_environment,我們可以繼續看下這個變量如何賦值的 ;
如下代碼賦值代碼都在thread.cpp中
thread->jni_environment();
// Returns the jni environment for this thread
JNIEnv* jni_environment() { return &_jni_environment; }
_jni_environment 又是線程的一個成員變量,所以我跟蹤到賦值的方法set_jni_functions
//JNI functiontable getter/setter for JVMTI jni function table interception API.
void set_jni_functions(struct JNINativeInterface_* functionTable) {
_jni_environment.functions = functionTable;
}
set_jni_functions此方法在線程的初始化流程進行調用,而線程創建是在Threads::create_vm中
Threads::create_vm
jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain)
{
// Attach the main thread to this os thread
JavaThread* main_thread = new JavaThread();
}
JavaThread::JavaThread(bool is_attaching_via_jni) :Thread()
{
initialize();
}
// A JavaThread is a normal Java thread
void JavaThread::initialize() {
// Initialize fields
set_saved_exception_pc(NULL);
set_threadObj(NULL);
_anchor.clear();
set_entry_point(NULL);
set_jni_functions(jni_functions()); //這個就是在線程的構造方法中調用
}
在此我們先總結下調用流 ,JavaThread構造方法 -> JavaThread::initialize -> JavaThread::set_jni_functions ,將jni_functions()返回值賦值給了JNIEnv* penv
jni_functions()
此函數在jni.cpp中
// Returns the function structure
struct JNINativeInterface_* jni_functions() {
return &jni_NativeInterface;
}
再看先jni_NativeInterface的定義,就是一對函數地址組成的結構體,這些函數都是以jni開頭,並且就定義在此文件中,此時我們回頭再看 (*env)->GetStaticMethodID 的定義就在這
// Structure containing all jni functions
struct JNINativeInterface_ jni_NativeInterface = {
//....
jni_GetStaticMethodID,
jni_CallStaticObjectMethod
//....
}
到此我們知道JavaMain 方法中,首先初始化JVM,初始jvm的時候,創建對應的線程,對JniEnv進行了賦值,JVM初始化流程比較多,我們先暫時放放, 然後就是去加載Main Class, 然後通過JniEnv的方法GetStaticMethodID獲取Main方法,然後通過JniEnv方法CallStaticObjectMethod調用Java main方法