在之前的文章中已经介绍了如何在Mac上编译及调试OpenJDK10源码,
那么,一个Java实例从开始运行至结束经历了什么?本文将从JVM源码角度一探究竟,深入剖析HotSpot其创建流程。
main.c # main()
程序主入口位于src/java.base/share/native/launcher/main.c
int
main(int argc, char **argv)
{
int margc;
char** margv;
int jargc;
char** jargv;
const jboolean const_javaw = JNI_FALSE;
...
#endif /* WIN32 */
return JLI_Launch(margc, margv,
jargc, (const char**) jargv,
0, NULL,
VERSION_STRING,
DOT_VERSION,
(const_progname != NULL) ? const_progname : *margv,
(const_launcher != NULL) ? const_launcher : *margv,
jargc > 0,
const_cpwildcard, const_javaw, 0);
}
main返回了JLI_Launch()
函数,位于src/java.base/share/native/libjli/java.c
java.c # JLI_Launch()
/*
* 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, /* UNUSED 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 /* unused */
)
{
int mode = LM_UNKNOWN;
char *what = NULL;
char *main_class = NULL;
int ret;
InvocationFunctions ifn;
jlong start, end;
char jvmpath[MAXPATHLEN];
char jrepath[MAXPATHLEN];
char jvmcfg[MAXPATHLEN];
/*
* 确保运行适当的JRE
*
* SelectVersion() has several responsibilities:
*
* 1) Disallow specification of another JRE. With 1.9, another
* version of the JRE cannot be invoked.
* 2) Allow for a JRE version to invoke JDK 1.9 or later. Since
* all mJRE directives have been stripped from the request but
* the pre 1.9 JRE [ 1.6 thru 1.8 ], it is as if 1.9+ has been
* invoked from the command line.
*/
SelectVersion(argc, argv, &main_class);
// 创建运行环境,如检查系统使用的数据模型(32bit、64bit),获取使用的JRE路径,找到jvm.cfg解析已知的vm类型
// 设置新的LD_LIBRARY_PATH变量
CreateExecutionEnvironment(&argc, &argv,
jrepath, sizeof(jrepath),
jvmpath, sizeof(jvmpath),
jvmcfg, sizeof(jvmcfg));
// 加载JVM
// 通过jvmpath找到libjvm.so 返回以下方法
// JNI_CreateJavaVM
// JNI_GetDefaultJavaVMInitArgs
// GetCreatedJavaVMs
// 的符号地址返回,挂载到InvocationFunctions以便后续调用
if (!LoadJavaVM(jvmpath, &ifn)) {
return(6);
}
if (IsJavaArgs()) {
// 转换命令行参数 如:javac -cp foo:foo/"*" -J-ms32m
/* Preprocess wrapper arguments */
TranslateApplicationArgs(jargc, jargv, &argc, &argv);
/*
* 添加了三个VM选项
* -Denv.class.path 用户设置的CLASSPATH变量,如果CLASSPATH显式设置了tools.jar
* 则可以反编译VM的工具类sun.tools.*
* -Dapplication.home 应用程序目录
* -Djava.class.path 应用程序的类文件目录
*/
if (!AddApplicationOptions(appclassc, appclassv)) {
return(1);
}
} else {
/* Set default CLASSPATH */
char* cpath = getenv("CLASSPATH");
if (cpath != NULL) {
SetClassPath(cpath);
}
}
/* 解析命令行参数-jar -cp、-version、-*path、-X*等参数
*
* 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);
}
// 设置classpath
/* Override class path if -jar flag was specified */
if (mode == LM_JAR) {
SetClassPath(what); /* Override class path */
}
...
return JVMInit(&ifn, threadStackSize, argc, argv, mode, what, ret);
}
继续跟进JVMInit()
方法,位于src/java.base/macosx/native/libjli/java_md_macosx.c
,
> 注:
> 由于我的系统是Mac,所以对应的是java_md_macosx.c,
> 其他系统位于目录src/java.base
对应操作系统类型下的java_md.c或java_md_xxx.c
java_md_macosx.c # JVMInit()
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;
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 {
// block当前线程并且在新线程中继续执行
// 至于为什么在新线程中创建JVM见引用https://bugs.openjdk.java.net/browse/JDK-6316197
return ContinueInNewThread(ifn, threadStackSize, argc, argv, mode, what, ret);
}
}
JVMInit
方法紧接着又return回java.c
的ContinueInNewThread()
方法
java.c # ContinueInNewThread()
int
ContinueInNewThread(InvocationFunctions* ifn, jlong threadStackSize,
int argc, char **argv,
int mode, char *what, int ret)
{
/*
* 指定线程大小
* If user doesn't specify stack size, check if VM has a preference.
* Note that HotSpot no longer supports JNI_VERSION_1_1 but it will
* return its default stack size through the init args structure.
*/
if (threadStackSize == 0) {
struct JDK1_1InitArgs args1_1;
memset((void*)&args1_1, 0, sizeof(args1_1));
args1_1.version = JNI_VERSION_1_1;
ifn->GetDefaultJavaVMInitArgs(&args1_1); /* ignore return value */
if (args1_1.javaStackSize > 0) {
threadStackSize = args1_1.javaStackSize;
}
}
{ /* 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;
}
}
ContinueInNewThread()
方法调用了执行方法ContinueInNewThread0()
java.c # ContinueInNewThread0()
/*
* Block current thread and continue execution in a new thread
*/
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);
}
pthread_attr_setguardsize(&attr, 0); // no pthread guard page on java threads
if (pthread_create(&tid, &attr, (void *(*)(void*))continuation, (void*)args) == 0) {
void * tmp;
pthread_join(tid, &tmp);
rslt = (int)(intptr_t)tmp;
} else {
/*
* 调用JNI_CreateJavaVM执行创建虚拟机
*
* Continue execution in current thread if for some reason (e.g. out of
* memory/LWP) a new thread can't be created. This will likely fail
* later in continuation as JNI_CreateJavaVM needs to create quite a
* few new threads, anyway, just give it a try..
*/
rslt = continuation(args);
}
pthread_attr_destroy(&attr);
return rslt;
}
ContinueInNewThread0()
中最终执行了名为continuation
的JNICALL,而这个的JNICALL正是上一步传过来的JavaMain
,
单看JavaMain
这个名字就好熟悉,有木有?接下来我们看看JavaMain
的庐山真面目
java.c # JavaMain()
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;
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;
RegisterThread();
// ================================
// 初始化虚拟机
// ================================
/* Initialize the virtual machine */
start = CounterGet();
if (!InitializeJVM(&vm, &env, &ifn)) {
JLI_ReportErrorMessage(JVM_ERROR1);
exit(1);
}
...
// 如果输入了-version或-showversion参数
if (printVersion || showVersion) {
PrintJavaVersion(env, showVersion);
CHECK_EXCEPTION_LEAVE(0);
if (printVersion) {
LEAVE();
}
}
// 如果jar文件和类名均未指定则输出默认usage信息
/* 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 */
ret = 1;
/*
* 加载Java程序的main方法,如果没找到则退出
*
* Get the application's main class. It also checks if the main
* method exists.
*
* 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.
*/
mainClass = LoadMainClass(env, mode, what);
CHECK_EXCEPTION_NULL_LEAVE(mainClass);
/*
* 获取程序主类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);
// 构建main方法参数列表
/* Build platform specific argument array */
mainArgs = CreateApplicationArgs(env, argv, argc);
CHECK_EXCEPTION_NULL_LEAVE(mainArgs);
if (dryRun) {
ret = 0;
LEAVE();
}
/*
* 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.
*/
PostJVMInit(env, appClass, vm);
CHECK_EXCEPTION_LEAVE(1);
/*
* 获取main方法ID
*
* 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.
*/
mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
"([Ljava/lang/String;)V");
CHECK_EXCEPTION_NULL_LEAVE(mainID);
// 调用main方法
/* 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();
}
可见JavaMain()
是Java主程序的native调用。
在该方法里会执行虚拟机的初始化,获取Java程序主类及main方法,然后通过JNI调用main方法,
自此,整个JVM进程执行结束,最终退出。
值得注意的是:
该方法中调用的InitializeJVM()
方法会执行一系列关于虚拟机的分配、挂载、初始化等工作,下篇文章我们继续详细深入介绍。