HotSpot一般會經過java.exe或javaw.exe來調用/jdk/src/share/bin/main.c文件中的main()函數來啓動虛擬機,使用Eclipse進行調試時,也會調用到這個入口。main.c的main()函數負責建立運行環境,以及啓動一個全新的線程去執行JVM的初始化和調用Java程序的main()方法。main()函數最終會阻塞當前線程,同時用另一個線程去調用JavaMain()函數。main()函數的調用棧以下: html
main() main.c
JLI_Launch() java.c
JVMInit() java_md_solinux.c
ContinueInNewThread() java.c
ContinueInNewThread0() java_md_solinux.c
pthread_join() pthread_join.c
調用鏈的順序從上到下,下面簡單介紹一下涉及到的相關方法。java
首先就是main()方法,方法的實現以下:linux
源代碼位置:/openjdk/jdk/src/share/bin/main.c
#ifdef JAVAW
char **__initenv;
int WINAPI WinMain(HINSTANCE inst, HINSTANCE previnst, LPSTR cmdline, int cmdshow){
int margc;
char** margv;
const jboolean const_javaw = JNI_TRUE;
__initenv = _environ;
#else /* JAVAW */
int main(int argc, char **argv){
int margc;
char** margv;
const jboolean const_javaw = JNI_FALSE;
#endif /* JAVAW */
#ifdef _WIN32
{
int i = 0;
if (getenv(JLDEBUG_ENV_ENTRY) != NULL) {
printf("Windows original main args:\n");
for (i = 0 ; i < __argc ; i++) {
printf("wwwd_args[%d] = %s\n", i, __argv[i]);
}
}
}
JLI_CmdToArgs(GetCommandLine());
margc = JLI_GetStdArgc();
// add one more to mark the end
margv = (char **)JLI_MemAlloc((margc + 1) * (sizeof(char *)));
{
int i = 0;
StdArg *stdargs = JLI_GetStdArgs();
for (i = 0 ; i < margc ; i++) {
margv[i] = stdargs[i].arg;
}
margv[i] = NULL;
}
#else /* *NIXES */
margc = argc;
margv = argv;
#endif /* WIN32 */
return JLI_Launch(margc, margv,
sizeof(const_jargs) / sizeof(char *), const_jargs,
sizeof(const_appclasspath) / sizeof(char *), const_appclasspath,
FULL_VERSION,
DOT_VERSION,
(const_progname != NULL) ? const_progname : *margv,
(const_launcher != NULL) ? const_launcher : *margv,
(const_jargs != NULL) ? JNI_TRUE : JNI_FALSE,
const_cpwildcard, const_javaw, const_ergo_class);
}
這個方法是Windows、UNIX、Linux以及Mac OS操做系統中C/C++的入口函數,而Windows的入口函數和其它的不太同樣,因此爲了儘量重用代碼,這裏使用#ifdef條件編譯,因此對於基於Linux內核的Ubuntu來講,最終編譯的代碼實際上是以下的樣子: 數組
int main(int argc, char **argv){
int margc;
char** margv;
const jboolean const_javaw = JNI_FALSE;
margc = argc;
margv = argv;
return JLI_Launch(margc, margv,
sizeof(const_jargs) / sizeof(char *), const_jargs,
sizeof(const_appclasspath) / sizeof(char *), const_appclasspath,
FULL_VERSION,
DOT_VERSION,
(const_progname != NULL) ? const_progname : *margv,
(const_launcher != NULL) ? const_launcher : *margv,
(const_jargs != NULL) ? JNI_TRUE : JNI_FALSE,
const_cpwildcard, const_javaw, const_ergo_class);
}
第一個參數,int型的argc,爲整型,用來統計程序運行時發送給main函數的命令行參數的個數;第二個參數,char型的argv[],爲字符串數組,用來存放指向的字符串參數的指針數組,每個元素指向一個參數。app
JLI_Launch()函數進行了一系列必要的操做,如libjvm.so的加載、參數解析、Classpath的獲取和設置、系統屬性的設置、JVM 初始化等。函數會調用LoadJavaVM()加載libjvm.so並初始化相關參數,調用語句以下:jvm
LoadJavaVM(jvmpath, &ifn)
其中jvmpath就是"/home/mazhi/workspace/openjdk/build/linux-x86_64-normal-server-slowdebug/jdk/lib/amd64/server/libjvm.so",也就是libjvm.so的存儲路徑,而ifn是InvocationFunctions類型變量,InvocationFunctions的定義以下: 函數
源代碼位置:/home/mazhi/workspace/openjdk/jdk/src/share/bin/java.h
typedef jint (JNICALL *CreateJavaVM_t)(JavaVM **pvm, void **env, void *args);
typedef jint (JNICALL *GetDefaultJavaVMInitArgs_t)(void *args);
typedef jint (JNICALL *GetCreatedJavaVMs_t)(JavaVM **vmBuf, jsize bufLen, jsize *nVMs);
typedef struct {
CreateJavaVM_t CreateJavaVM;
GetDefaultJavaVMInitArgs_t GetDefaultJavaVMInitArgs;
GetCreatedJavaVMs_t GetCreatedJavaVMs;
} InvocationFunctions;
能夠看到結構體InvocationFunctions中定義了3個函數指針,3個函數的實如今libjvm.so這個動態連接庫中,查看LoadJavaVM()函數後就能夠看到有以下實現: ui
ifn->CreateJavaVM = (CreateJavaVM_t) dlsym(libjvm, "JNI_CreateJavaVM");
ifn->GetDefaultJavaVMInitArgs = (GetDefaultJavaVMInitArgs_t)dlsym(libjvm, "JNI_GetDefaultJavaVMInitArgs");
ifn->GetCreatedJavaVMs = (GetCreatedJavaVMs_t) dlsym(libjvm, "JNI_GetCreatedJavaVMs");
因此經過函數指針調用時,最終會調用到libjvm.so中對應的以JNI_Xxx開頭的方法,其中JNI_CreateJavaVM()方法會在InitializeJVM()函數中調用,用來初始化2個JNI調用時很是重要的2個參數JavaVM和JNIEnv,後面在介紹JNI時還會詳細介紹,這裏不作過多介紹。spa
JVMInit()函數的源代碼以下:操作系統
位置:/openjdk/jdk/src/solaris/bin/java_md_solinux.c
int JVMInit(InvocationFunctions* ifn, jlong threadStackSize,
int argc, char **argv,
int mode, char *what, int ret){
...
return ContinueInNewThread(ifn, threadStackSize, argc, argv, mode, what, ret);
}
在JVMInit()函數中調用的ContinueInNewThread()函數的實現以下:
源代碼位置:/openjdk/jdk/src/share/bin/java.c
int ContinueInNewThread(InvocationFunctions* ifn, jlong threadStackSize,
int argc, char **argv,
int mode, char *what, int ret){
...
{ /* Create a new thread to create JVM and invoke main method */
JavaMainArgs args;
int rslt;
args.argc = argc;
args.argv = argv;
args.mode = mode;
args.what = what;
args.ifn = *ifn;
rslt = ContinueInNewThread0(JavaMain, threadStackSize, (void*)&args);
/* If the caller has deemed there is an error we
* simply return that, otherwise we return the value of
* the callee
*/
return (ret != 0) ? ret : rslt;
}
}
在調用ContinueInNewThread0()函數時,傳遞了JavaMain函數指針和調用此函數須要的參數args。
ContinueInNewThread()函數調用的ContinueInNewThread0()函數的實現以下:
位置:/openjdk/jdk/src/solaris/bin/java_md_solinux.c
int ContinueInNewThread0(int (JNICALL *continuation)(void *), jlong stack_size, void * args) {
int rslt;
...
pthread_t tid;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
if (stack_size > 0) {
pthread_attr_setstacksize(&attr, stack_size);
}
if (pthread_create(&tid, &attr, (void *(*)(void*))continuation, (void*)args) == 0) {
void * tmp;
pthread_join(tid, &tmp); // 當前線程會阻塞在這裏
rslt = (int)tmp;
}
pthread_attr_destroy(&attr);
...
return rslt;
}
Linux 系統下(後面所說的Linux系統都是指基於Linux內核的操做系統)建立一個 pthread_t 線程,而後使用這個新建立的線程執行JavaMain()函數。
方法的第一個參數int (JNICALL continuation)(void )接收的就是JavaMain()函數的指針。關於指針函數與函數指針、以及Linux下建立線程的相關知識點後面會介紹,到時候這裏會給出連接。
下面就來看一下JavaMain()函數的實現,以下:
位置:/openjdk/jdk/src/share/bin/java.c
int JNICALL JavaMain(void * _args){
JavaMainArgs *args = (JavaMainArgs *)_args;
int argc = args->argc;
char **argv = args->argv;
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;
// InitializeJVM 初始化JVM,給JavaVM和JNIEnv對象正確賦值,經過調用InvocationFunctions結構體下
// 的CreateJavaVM()函數指針來實現,該指針在LoadJavaVM()函數中指向libjvm.so動態連接庫中JNI_CreateJavaVM()函數
if (!InitializeJVM(&vm, &env, &ifn)) {
JLI_ReportErrorMessage(JVM_ERROR1);
exit(1);
}
// ...
mainClass = LoadMainClass(env, mode, what);
appClass = GetApplicationClass(env);
mainID = (*env)->GetStaticMethodID(env, mainClass, "main", "([Ljava/lang/String;)V");
/* Build platform specific argument array */
mainArgs = CreateApplicationArgs(env, argv, argc);
/* Invoke main method. */
(*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);
// ...
}
代碼主要就是找出Java源代碼的main()方法,而後調用並執行。
以上步驟都還在當前線程的控制下。當控制權轉移到Test.main()以後當前線程就再也不作其它事兒了,等Test.main()函數返回以後,當前線程會清理和關閉JVM。調用本地函數jni_DetachCurrentThread()斷開與主線程的鏈接。當成功與主線程斷開鏈接後,當前線程一直等待程序中全部的非守護線程所有執行結束,而後調用本地函數jni_DestroyJavaVM()對JVM執行銷燬。
相關文章的連接以下:
一、在Ubuntu 16.04上編譯OpenJDK8的源代碼
關注我的博客www.classloading.com或公衆號,有HotSpot源碼剖析系列文章!