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 version resource file (.rc) : windows 版本资源文件(.rc)
$(JDK_OUTPUTDIR)/objs/java_objs,\ #9 different output dir : 不同的输出目录
true #10 if set, link statically with c runtime on windows. : 如果设置参数10 , 静态链接c运行时,在windows上.
)
)
# jmap
$(eval $(call SetupLauncher,
jmap,
-DJAVA_ARGS='{ "-J-ms8m"$(COMMA)
"-J-Dsun.jvm.hotspot.debugger.useProcDebugger"$(COMMA)
"-J-Dsun.jvm.hotspot.debugger.useWindbgDebugger"$(COMMA)
"sun.tools.jmap.JMap"$(COMMA) }'
-DAPP_CLASSPATH='{ "/lib/tools.jar"$(COMMA) "/lib/sa-jdi.jar"$(COMMA) "/classes" }' ,
,,,,,,,,
Info-privileged.plist)) # Parameter 11 if set, override plist file on macosx. : 如果设置,覆盖 plist文件在 macosx上
// windows 上的java
ifeq ($(OPENJDK_TARGET_OS), windows)
$(eval $(call SetupLauncher,javaw,
-DJAVAW -DEXPAND_CLASSPATH_WILDCARDS,,,user32.lib comctl32.lib,
$(JDK_OUTPUTDIR)/objs/jli_static.lib, $(JAVA_RC_FLAGS),
$(JDK_TOPDIR)/src/windows/resource/java.rc,,true))
endif
FULL_VERSION 和 DOT_VERSION
分别表示jdk的版本的2种表现形式,看图示
JLI_Launch() java.c
openjdk-jdk8u/jdk/src/share/bin/java.c
jdk/src/share/bin/java.c
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* p