0. 前言
一个类被编译为class file之后,使用java命令去执行,暂时抛开OS层面的syscall 及 glibc的入口函数, java中的main方法执行,经历了什么样的过程?要执行main方法,必须要有vm支持,那vm又是如何去构建的?
本章我们把握整体流程,建立一个初步认识。
1. 整体流程
1.main() main.c
1.JLI_Launch() java.c
1.1 CreateExecutionEnvironment() java_md_solinux.c
SetExecname() java_md_solinux.c
1.2 SetJvmEnvironment() java.c
1.3 LoadJavaVM() java_md_solinux.c 通过syscall加载libjvm.so 或 jvm.dll
1.4 SetClassPath(..) 设置目标为class文件或jar包路径
1.5 JvmInit() java_md_solinux.c
1.5.1 ContinueInNewThread() java.c
1.5.2 ContinueInNewThread0() java_md_solinux.c, 创建并等待“main线程"运行结束。java栈的大小在linux64位下默认1024k,32位默认320k.
2.JavaMain() java.c main线程的入口
2.1 InitializeJVM() java.c 初始化vm
JNI_CreateJavaVM() jni.cpp
create_vm() thread.cpp 创建vm的主要工作都在这个函数里完成
2.2 LoadMainClass() 加载main class
2.3 CallStaticVoidMethod() 真正去执行java类中的main函数
2. main() main.c - 一切从这里开始
使用如下命令即可在Linux系统的shell环境下启动Java程序
java [options] xxx.class param1 param2 ... paramn
当执行命令时,shell会创建新进程来执行该命令,由于JVM是用C/C++实现的,我们只需定位到main函数即可。至于glibc的入口函数,此处不再赘述。
main.c - main(..) / WinMain(..)
openjdk-jdk8u/jdk/src/share/bin/main.c
openjdk-jdk8u/jdk/src/share/bin/main.c
// 这里我们忽略掉源代码中关于windows部分的编译选项
int main(int argc, char **argv)
{
int margc; // 命令参数个数,从java开始数,空格隔开,假设命令是:java xxx.class 1 2 3,那么此时margc = 5
char** margv; // 参数指针
const jboolean const_javaw = JNI_FALSE;
margc = argc;
margv = argv;
// 调用java.c的JLI_Launch函数
return JLI_Launch(margc, margv,
sizeof(const_jargs) / sizeof(char *), // const_jargs字符串个数
const_jargs, // 编译选项[JAVA_ARGS],默认为空,一般只有java tools编译时使用JAVA_ARGS
sizeof(const_appclasspath) / sizeof(char *), // const_appclasspath的个数
const_appclasspath, // 1. 编译选项[JAVA_ARGS][APP_CLASSPATH],为APP_CLASSPATH选项的值
// 2. 只有编译选项[JAVA_ARGS], 默认为{ "/lib/tools.jar", "/classes" }
// 3. 为空
FULL_VERSION, // 编译选项[DFULL_VERSION,编译时必须填,否则编译不过
DOT_VERSION, // 编译选项 [JDK_MAJOR_VERSION] "." [JDK_MINOR_VERSION] 组成,编译时必须填,否则不通过
(const_progname != NULL) ? const_progname : *margv, // 编译选项[JAVA_ARGS] 或 [PROGNAME]确定,这里是java
(const_launcher != NULL) ? const_launcher : *margv, // 编译选项[LAUNCHER_NAME],这里是java
(const_jargs != NULL) ? JNI_TRUE : JNI_FALSE, // 在java 这里是false
const_cpwildcard, // 编译选项[EXPAND_CLASSPATH_WILDCARDS]决定,定义EXPAND_CLASSPATH_WILDCARDS 时为true,否则为false。 这里是true
const_javaw, // 编译选项[JAVAW决定,windows]下为true,linux下为false。
const_ergo_class);// 编译选项[NEVER_ACT_AS_SERVER_CLASS_MACHINE] / [ALWAYS_ACT_AS_SERVER_CLASS_MACHINE],决定ergo_policy,可以决定jvm运行哪种模式下
}
TIP:JDK编译选项
编译选项
在JDK的bin目录下,几乎所有的命令程序 都是通过同一个main函数编译生成的。
这些命令包括java、javac、jconsole、jps、jmap和jhat等。
根据编译时选项的不同,这些命令在执行时会进行不同的处理。具体的编译配置信息可参见附录章节。
$(eval $(call SetupLauncher,
java, \ #1 launcher 名称
-DEXPAND_CLASSPATH_WILDCARDS,\ #2 CFLAGS
, \ #3 LDFLAGS
, \ #4 LDFLAGS_SUFFIX_posix
user32.lib comctl32.lib, \ #5 LDFLAGS_SUFFIX_windows
$(JDK_OUTPUTDIR)/objs/jli_static.lib,\ #6 optional Windows JLI library (full path) : windows jli库的全路径
$(JAVA_RC_FLAGS), \ #7 optional Windows resource (RC) flags : windows 资源文件标识
$(JDK_TOPDIR)/src/windows/resource/java.rc,\ #8 optional Windows vers