上一篇粗略講了下jvm的啓動過程,但不少路子還沒跑通。其中很是核心的,加載vm的過程。這個能夠在hotspot中找到端倪。但java啓動,還有幾個線程能夠看看。java
在java.c中,咱們能夠看到一個JavaMain方法,不知從何而來,但很像是直接加載java入口的方法。linux
// 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(); // 初始化jvm,失敗則退出 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. */ // 加載 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. */ // 獲取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(); } /* * 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; 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; } // 初始化jvm, 主要是調用 CreateJavaVM() 方法,進行建立jvm操做 /* * 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; if (JLI_IsTraceLauncher()) { int i = 0; printf("JavaVM args:\n "); printf("version 0x%08lx, ", (long)args.version); printf("ignoreUnrecognized is %s, ", args.ignoreUnrecognized ? "JNI_TRUE" : "JNI_FALSE"); printf("nOptions is %ld\n", (long)args.nOptions); for (i = 0; i < numOptions; i++) printf(" option[%2d] = '%s'\n", i, args.options[i].optionString); } r = ifn->CreateJavaVM(pvm, (void **)penv, &args); JLI_MemFree(options); return r == JNI_OK; }
略去核心加載jvm的實現,要加載main類,仍是比較簡單的。主要就是經過前面查找出的main_class, 而後經過在jvm的實例中獲取到的各方法的函數指針,而後按照字節碼的規範,調用 MainClass.main(String[]) 方法。固然了,爲了兼容其餘非main的場景,它還有不少附加處理邏輯。macos
前面講了加載main方法的過程,大體理解了c如何啓動調用java的main的。那麼,這又是如何調用準備的呢,在這以前都須要作哪些準備呢?windows
實際上,這是平臺相關的實現。app
// share/bin/java.c /* * Entry point. */ int JLI_Launch(int argc, char ** argv, /* main argc, argc */ int jargc, const char** jargv, /* java args */ int appclassc, const char** appclassv, /* app classpath */ const char* fullversion, /* full version defined */ const char* dotversion, /* dot version defined */ const char* pname, /* program name */ const char* lname, /* launcher name */ jboolean javaargs, /* JAVA_ARGS */ jboolean cpwildcard, /* classpath wildcard*/ jboolean javaw, /* windows-only javaw */ jint ergo /* ergonomics class policy */ ) { int mode = LM_UNKNOWN; char *what = NULL; char *cpath = 0; char *main_class = NULL; int ret; InvocationFunctions ifn; jlong start, end; char jvmpath[MAXPATHLEN]; char jrepath[MAXPATHLEN]; char jvmcfg[MAXPATHLEN]; _fVersion = fullversion; _dVersion = dotversion; _launcher_name = lname; _program_name = pname; _is_java_args = javaargs; _wc_enabled = cpwildcard; _ergo_policy = ergo; // 初始化啓動器 InitLauncher(javaw); // 打印狀態 DumpState(); // 跟蹤調用啓動 if (JLI_IsTraceLauncher()) { int i; printf("Command line args:\n"); for (i = 0; i < argc ; i++) { printf("argv[%d] = %s\n", i, argv[i]); } AddOption("-Dsun.java.launcher.diag=true", NULL); } /* * Make sure the specified version of the JRE is running. * * There are three things to note about the SelectVersion() routine: * 1) If the version running isn't correct, this routine doesn't * return (either the correct version has been exec'd or an error * was issued). * 2) Argc and Argv in this scope are *not* altered by this routine. * It is the responsibility of subsequent code to ignore the * arguments handled by this routine. * 3) As a side-effect, the variable "main_class" is guaranteed to * be set (if it should ever be set). This isn't exactly the * poster child for structured programming, but it is a small * price to pay for not processing a jar file operand twice. * (Note: This side effect has been disabled. See comment on * bugid 5030265 below.) */ // 解析命令行參數,選擇一jre版本 SelectVersion(argc, argv, &main_class); CreateExecutionEnvironment(&argc, &argv, jrepath, sizeof(jrepath), jvmpath, sizeof(jvmpath), jvmcfg, sizeof(jvmcfg)); if (!IsJavaArgs()) { // 設置一些特殊的環境變量 SetJvmEnvironment(argc,argv); } ifn.CreateJavaVM = 0; ifn.GetDefaultJavaVMInitArgs = 0; if (JLI_IsTraceLauncher()) { start = CounterGet(); } // 加載VM, 重中之重 if (!LoadJavaVM(jvmpath, &ifn)) { return(6); } if (JLI_IsTraceLauncher()) { end = CounterGet(); } JLI_TraceLauncher("%ld micro seconds to LoadJavaVM\n", (long)(jint)Counter2Micros(end-start)); ++argv; --argc; // 解析更多參數信息 if (IsJavaArgs()) { /* Preprocess wrapper arguments */ TranslateApplicationArgs(jargc, jargv, &argc, &argv); if (!AddApplicationOptions(appclassc, appclassv)) { return(1); } } else { /* Set default CLASSPATH */ cpath = getenv("CLASSPATH"); if (cpath == NULL) { cpath = "."; } SetClassPath(cpath); } /* Parse command line options; if the return value of * ParseArguments is false, the program should exit. */ // 解析參數 if (!ParseArguments(&argc, &argv, &mode, &what, &ret, jrepath)) { return(ret); } /* Override class path if -jar flag was specified */ if (mode == LM_JAR) { SetClassPath(what); /* Override class path */ } /* set the -Dsun.java.command pseudo property */ SetJavaCommandLineProp(what, argc, argv); /* Set the -Dsun.java.launcher pseudo property */ SetJavaLauncherProp(); /* set the -Dsun.java.launcher.* platform properties */ SetJavaLauncherPlatformProps(); return JVMInit(&ifn, threadStackSize, argc, argv, mode, what, ret); } // macos/bin/java_md_macos.c // MacOSX we may continue in the same thread int JVMInit(InvocationFunctions* ifn, jlong threadStackSize, int argc, char **argv, int mode, char *what, int ret) { if (sameThread) { JLI_TraceLauncher("In same thread\n"); // need to block this thread against the main thread // so signals get caught correctly __block int rslt = 0; NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; { NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock: ^{ JavaMainArgs args; args.argc = argc; args.argv = argv; args.mode = mode; args.what = what; args.ifn = *ifn; // 調用 JavaMain() rslt = JavaMain(&args); }]; /* * We cannot use dispatch_sync here, because it blocks the main dispatch queue. * Using the main NSRunLoop allows the dispatch queue to run properly once * SWT (or whatever toolkit this is needed for) kicks off it's own NSRunLoop * and starts running. */ [op performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:YES]; } [pool drain]; return rslt; } else { return ContinueInNewThread(ifn, threadStackSize, argc, argv, mode, what, ret); } }
以上是mac調用 javaMain的方式。在windows, 以及linux上則稍有不一樣。後續再說。jvm