1. 引言
JVM(Java Virtual Machine)是 Java 程序的运行基石。理解 JVM 的生命周期,即从启动到销毁的完整过程,是深入掌握 Java 运行时机制的第一步。本文将从 OpenJDK8 源码出发,概述在linux系统JVM 的启动、初始化、执行与销毁四个核心阶段的调用链,帮助读者建立对 JVM 整体架构的宏观认知。
2. JVM 启动入口
JVM 的启动入口位于 src/share/bin/main.c 文件中的 main() 函数。
// src/share/bin/main.c
int
main(int argc, char **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);
}
进入main函数,JVM进程开始运行。该函数相对简单,调用了JLI_Launch函数
// src/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 */
)
{
...
CreateExecutionEnvironment(&argc, &argv,
jrepath, sizeof(jrepath),
jvmpath, sizeof(jvmpath),
jvmcfg, sizeof(jvmcfg));
ifn.CreateJavaVM = 0;
ifn.GetDefaultJavaVMInitArgs = 0;
...
if (!LoadJavaVM(jvmpath, &ifn)) {
return(6);
}
...
return JVMInit(&ifn, threadStackSize, argc, argv, mode, what, ret);
}
JLI_Launch函数需要重点关注的是调用了CreateExecutionEnvironment、LoadJavaVM和JVMInit等函数。
CreateExecutionEnvironment函数创建运行环境
LoadJavaVM函数主要是加载JVM共享链接库libvjm.so,将函数指针ifn->CreateJavaVM指向JNI_CreateJavaVM,ifn->GetDefaultJavaVMInitArgs指向JNI_GetDefaultJavaVMInitArgs,ifn->GetCreatedJavaVMs指向JNI_GetCreatedJavaVMs。
// 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函数去开启新线程。
// src/share/bin/java.c
int
ContinueInNewThread(InvocationFunctions* ifn, jlong threadStackSize,
int argc, char **argv,
int mode, char *what, int ret)
{
...
rslt = ContinueInNewThread0(JavaMain, threadStackSize, (void*)&args);
return (ret != 0) ? ret : rslt;
}
}
ContinueInNewThread函数调用了ContinueInNewThread0函数去开启新线程。
如果成功创建新线程,由新线程调用JavaMain函数。到目前为止,在linux内核角度来看,linux内核已经为该JVM进程创建了两个TASK_STRUCT.。一个是之前main函数所在的TASK_STRUCT暂且叫它A,一个是新线程所在的TASK_STRUCT暂且叫它B。A关联的线程会一直等待B关联的线程运行完再继续运行。
如果创建新线程失败,则由原有的线程调用JavaMain函数。到目前为止,在linux内核角度来看,linux内核已经为该JVM进程创建了一个TASK_STRUCT.
// src/share/bin/java.c
int JNICALL
JavaMain(void * _args)
{
...
if (!InitializeJVM(&vm, &env, &ifn)) {
JLI_ReportErrorMessage(JVM_ERROR1);
exit(1);
}
...
mainClass = LoadMainClass(env, mode, what);
..
mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
"([Ljava/lang/String;)V");
...
mainArgs = CreateApplicationArgs(env, argv, argc);
...
(*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);
..
LEAVE();
}
JavaMain函数中调用了InitializeJVM、LoadMainClass、(*env)->GetStaticMethodID、CreateApplicationArgs、 (*env)->CallStaticVoidMethod等关键函数和宏LEAVE().
InitializeJVM函数极为重要,做了很多事情,初始化ClassLoader,创建了堆并进行初始化,创建垃圾收集器和相应的垃圾收集策略,后面在详解InitializeJVM再详细展开。
LoadMainClass函数找到并加载JAVA应用的Main Class.
(*env)->GetStaticMethodID获取JAVA应用Main Class的main方法ID。
CreateApplicationArgs函数创建JAVA应用Main Class main方法的参数。
(*env)->CallStaticVoidMethod函数开始调用Java应用Main Class的main方法,这里开始运行JAVA程序代码。
宏LEAVE()在JAVA应用程序执行完后对JVM进行销毁。
至此,JVM生命周期源码概览结束。
