以openJDK8源码为基础,分析JVM启动过程,在openJDK中目录/openjdk/hotspot/src/share/tools/目录下有launcherjvm启动函数,而在openJDK8中没有启动函数,函数入口位于
/openjdk/jdk/src/share/bin/main.c
由于JVM运行环境不同,大致有windows有I及linux两种,因此JVM代码中有一部分是平台无关的,有一部分是平台特有的,有一部分代码为了共用,使用条件编译#ifdef来区分不同平台以便复用代码,这里为了便于研究,只研究linux平台中的启动过程代码。
JVM启动过程方法函数调用过程大致如下:
1.main入口:/openjdk/jdk/src/share/bin/main.c
main方法调用JLI_Launch
2.JLI_Launch:/openjdk/jdk/src/share/bin/java.c
JLI_Launch方法中通过LoadJavaVM来使用JNI调用JVM库中的JNI_CreateJavaVM函数
JNI_CreateJavaVM /openjdk/hotspot/src/share/vm/prims/jni.cpp
进而调用create_vm函数来实现系统初始化等一系列设置
/openjdk/hotspot/src/share/vm/runtime/thread.cpp
JLI_Launch最后调用JVMInit来完成JVM的初始化启动等
3.JVMInit:/openjdk/jdk/src/solaris/bin/java_md_solinux.c
主要调用JavaMain方法
4.JavaMain: /openjdk/jdk/src/share/bin/java.c
作为java程序执行的主线程,加载主类,获取主方法,执行调用,获取返回值,异常处理,最终调用LEAVE方法
5.LEAVE: /openjdk/jdk/src/share/bin/java.c
实现虚拟机的销毁等一系列处理清除操作
create_vm是系统初始化的具体过程,JavaMain函数作为java程序执行的具体流程控制,这里具体大致学习一下,细节以后慢慢研究,先把大致运行流程搞清。
代码位置: /openjdk/jdk/src/share/bin/main.c
总体执行过程如下:
/* Initialize the virtual machine */
start = CounterGet();
if (!InitializeJVM(&vm, &env, &ifn)) {
JLI_ReportErrorMessage(JVM_ERROR1);
exit(1);
}
其具体执行过程如下所示:
/*
* 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;
}
初始化参数JavaVMInitArgs,调用JNI CreateJavaVM创建虚拟机,释放一部分参数内存资源
r = ifn->CreateJavaVM(pvm, (void **)penv, &args);
JLI_MemFree(options);
参数解析,由于具体细节过于复杂,这里先大致了解一下,等到对于JVM源码有一定的了解之后,再回来慢慢研究。
JavaVM **pvm 由以下源码可知,JavaVM是一个结构体,含有许多对于虚拟机的操作函数
typedef JavaVM_ JavaVM;
struct JavaVM_ {
const struct JNIInvokeInterface_ *functions;
struct JNIInvokeInterface_ {
void *reserved0;
void *reserved1;
void *reserved2;
jint (JNICALL *DestroyJavaVM)(JavaVM *vm);
jint (JNICALL *AttachCurrentThread)(JavaVM *vm, void **penv, void *args);
jint (JNICALL *DetachCurrentThread)(JavaVM *vm);
jint (JNICALL *GetEnv)(JavaVM *vm, void **penv, jint version);
jint (JNICALL *AttachCurrentThreadAsDaemon)(JavaVM *vm, void **penv, void *args);
};
JNIEnv **penv JNI函数接口 集合
struct JNIEnv_ {
const struct JNINativeInterface_ *functions;
InvocationFunctions *ifn 含有3个JNI函数,创建VM,获取VM默认的初始化参数,获取已经创建的VMs
/*
* Pointers to the needed JNI invocation API, initialized by LoadJavaVM.
*/
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;
LoadMainClass执行过程,主要是利用JNI方法来获取主类名称,进行类名处理,以及加载主类。
mainClass = LoadMainClass(env, mode, what);
/*
* 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();
}
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;
}
获取launcherhelper类,类路径/openjdk/jdk/src/share/classes/sun/launcher/LauncherHelper.java
jclass
GetLauncherHelperClass(JNIEnv *env)
{
if (helperClass == NULL) {
NULL_CHECK0(helperClass = FindBootStrapClass(env,
"sun/launcher/LauncherHelper"));
}
return helperClass;
}
获取到LauncherHelper后执行,checkAndLoadMain方法
NULL_CHECK0(mid = (*env)->GetStaticMethodID(env, cls,
"checkAndLoadMain",
"(ZILjava/lang/String;)Ljava/lang/Class;"));
checkAndLoadMain方法如下所示:获取类名,加载该类,以及检查main方法参数等
/**
* 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) {
一些应用可能没有main方法,获取application main class
/*
* 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);
mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
"([Ljava/lang/String;)V");
/* Invoke main method. */
(*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();
最终JVM启动过程大致如下所示: