HotSpot的启动过程
HotSpot通常会通过java.exe或javaw.exe来调用/jdk/src/share/bin/main.c文件中的main()函数来启动虚拟机,使用Eclipse进行调试时,也会调用到这个入口。main.c的main()函数负责创建运行环境,以及启动一个全新的线程去执行JVM的初始化和调用Java程序的main()方法。main()函数最终会阻塞当前线程,同时用另外一个线程去调用JavaMain()函数。main()函数的调用栈如下:
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
调用链的顺序从上到下,下面简单介绍一下涉及到的相关方法。
1、main()函数
首先就是main()方法,方法的实现如下:
源代码位置:/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[],为字符串数组,用来存放指向的字符串参数的指针数组,每一个元素指向一个参数。
2、JLI_Launch()函数
JLI_Launch()函数进行了一系列必要的操作,如libjvm.so的加载、参数解析、Classpath的获取和设置、系统属性的设置、JVM 初始化等。函数会调用LoadJavaVM()加载libjvm.so并初始化相关参数,调用语句如下:
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()函数后就可以看到有如下实现:
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时还会详细介绍,这里不做过多介绍。
3、JVMInit()函数
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);
}
4、ContinueInNewThread()函数
在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。
5、ContinueInNewthread0()函数
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()方法,然后调用并执行。
- 调用InitializeJVM()函数初始化JVM,主要就是初始化2个非常重要的变量JavaVM与JNIEnv,在这里不过多探讨这个问题,后面在讲解JNI调用时会详细介绍初始化过程。
- 调用LoadMainClass()函数获取Java程序的启动类,对于前面举过的实例来说,由于配置了参数 “com.test/Test", 所以会查找com.test.Test类。LoadMainClass()函数最终会调用libjvm.so中实现的JVM_FindClassFromBootLoader()方法来查找启动类,涉及到的逻辑比较多,后面在讲解类型的加载时会介绍。
- 调用GetStaticMethodId()函数查找Java启动方法,其实就是获取Test类中的main()方法。
- 调用JNIEnv中定义的CallStaticVoidMethod()方法,最终会调用JavaCalls::call()函数执行Test类中的main()方法。JavaCalls:call()函数是个非常重要的方法,后面在讲解方法执行引擎时会详细介绍。
以上步骤都还在当前线程的控制下。当控制权转移到Test.main()之后当前线程就不再做其它事儿了,等Test.main()函数返回之后,当前线程会清理和关闭JVM。调用本地函数jni_DetachCurrentThread()断开与主线程的连接。当成功与主线程断开连接后,当前线程一直等待程序中所有的非守护线程全部执行结束,然后调用本地函数jni_DestroyJavaVM()对JVM执行销毁。
相关文章的链接如下:
1、在Ubuntu 16.04上编译OpenJDK8的源代码
关注个人博客www.classloading.com或公众号,有HotSpot源码剖析系列文章!

HotSpot的启动过程的更多相关文章
- HotSpot的启动过程(配视频进行源码分析)
本文将详细介绍HotSpot的启动过程,启动过程涉及到的逻辑比较复杂,细节也比较多,为了让大家更快的了解这部分知识,我录制了对应的视频放到了B站上,大家可以参考. 第4节-HotSpot的启动过程 下 ...
- JVM启动过程
JVM启动过程包括:加载.连接.初始化 1.加载:就是将class文件加载到内存.详细的说是,将class文件加载到运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封 ...
- JVM系列(一):jvm启动过程速览
jvm是java的核心运行平台,自然是个非常复杂的系统.当然了,说jvm是个平台,实际上也是个泛称.准确的说,它是一个java虚拟机的统称,它并不指具体的某个虚拟机.所以,谈到java虚拟机时,往往我 ...
- Java虚拟机启动过程解析
一.序言 当我们在编写Java应用的时候,很少会注意Java程序是如何被运行的,如何被操作系统管理和调度的.带着好奇心,探索一下Java虚拟机启动过程. 1.素材准备 从Java源代码.Java字节码 ...
- zookeeper源码分析之一服务端启动过程
zookeeper简介 zookeeper是为分布式应用提供分布式协作服务的开源软件.它提供了一组简单的原子操作,分布式应用可以基于这些原子操作来实现更高层次的同步服务,配置维护,组管理和命名.zoo ...
- [原] KVM 虚拟化原理探究(2)— QEMU启动过程
KVM 虚拟化原理探究- QEMU启动过程 标签(空格分隔): KVM [TOC] 虚拟机启动过程 第一步,获取到kvm句柄 kvmfd = open("/dev/kvm", O_ ...
- Openfire的启动过程与session管理
说明 本文源码基于Openfire4.0.2. Openfire的启动 Openfire的启动过程非常的简单,通过一个入口初始化lib目录下的openfire.jar包,并启动一个 ...
- 探索 Linux 系统的启动过程
引言 之所以想到写这些东西,那是因为我确实想让大家也和我一样,把 Linux 桌面系统打造成真真正正日常使用的工具,而不是安装之后试用几把再删掉.我是真的在日常生活和工作中都使用 Linux,比如在 ...
- Linux内核启动过程概述
版权声明:本文原创,转载需声明作者ID和原文链接地址. Hi!大家好,我是CrazyCatJack.今天给大家带来的是Linux内核启动过程概述.希望能够帮助大家更好的理解Linux内核的启动,并且创 ...
随机推荐
- Linux RPM管理命令
RPM安装 rpm -ivh 包全名,其中,-i 表示安装,-v 表示显示详细信息,-h 表示显示进度 手动安装会有很多包依赖需要解决,如果是模块依赖,可以使用rpmfind进行查询解决 RPM升级 ...
- pytorch入门2.1构建回归模型初体验(模型构建)
pytorch入门2.x构建回归模型系列: pytorch入门2.0构建回归模型初体验(数据生成) pytorch入门2.1构建回归模型初体验(模型构建) pytorch入门2.2构建回归模型初体验( ...
- 【微信H5】 Redirect_uri参数错误解决方法
1 https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx14127af0bc9fd367&redirect_uri=http ...
- 【译】Introducing YARP Preview 1
1 YARP YARP是一个项目,用于创建反向代理服务器.它开始于我们注意到来自微软内部团队的一系列问题.他们要么为其服务构建反向代理,要么询问 API 和用于构建 API 的技术.因此我们决定让他们 ...
- Linux系统管理——Linux简介
UNIX与Linux发展史 UNIX发展历史 1.1965年,美国麻省理工学院(MIT),通用电气公司(GE)及AT&T的贝尔实验室联合开发Multics工程计划,其目标是开发一种交互式的具有 ...
- linu使用x之sz下载和rz上传
对于经常使用Linux系统的人员来说,少不了将本地的文件上传到服务器或者从服务器上下载文件到本地,rz / sz命令很方便的帮我们实现了这个功能,但是很多Linux系统初始并没有这两个命令.今天,我们 ...
- Charles 安装证书后依旧抓取不到https请求的解决方案
1.打开charles——>help——>SSL proxying——>Install Charles Root Certificate 证书安装后,抓取https的包 2.查看Pr ...
- Linux MySQL集群搭建之主从复制
前期准备 准备两台Linux,一主,一从,具体Linux安装MySQL操作步骤:点我直达 集群搭建 注意事项 一主可以多从 一从只能一主 关闭主从机器的防火墙策略 chkconfig iptables ...
- AIO,BIO,NIO,IO复用,同步,异步,阻塞和非阻塞
(1)什么是NIO(Non-blocked IO),AIO,BIO (2) 区别 (3)select 与 epoll,poll区别 1.什么是socket?什么是I/O操作? 什么是socket? 实 ...
- umi 调试
最近在umi. 设置一个layout字段, 结果左边菜单栏就出现了. 很神奇. 决定对这个库一探究竟. 我是一个喜欢看底层库的人,网上所有的启动方式都是yarn或者npm start 启动服务.然后 ...