我们先从执行一个class文件开始,通过命令java [options] xxx.class param1 param2 ... paramn
来执行一个java程序,在linux操作系统下的shell环境,执行一条命令时,shell会先fork一个新的进程来执行命令,一般根据规范程序的执行入口是main方法,jvm是c/c++实现的,这样我们只要找到该程序的main函数就行,通过查找得知main.c里面的main函数就是入口
char **__initenv;
// 这是windows的入口,忽略
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) // 非windows的入口从这开始
{
// 命令参数个数,从java开始数,空格隔开,假设命令是:java xxx.class 1 2 3,那么此时margc = 5
int margc;
// c/c++语言中没有像java一样直接描述字符串的对象,只能通过字符数组(也可以用指针表示)来表示字符串,存储表现形式看图2-1
char** margv;
const jboolean const_javaw = JNI_FALSE;
#endif /* JAVAW */
#ifdef _WIN32 // windows环境,忽略
{
int i = 0;
// 这里就是获取环境变量,如果设置了环境变量`_JAVA_LAUNCHER_DEBUG`,那么启动jvm,就会把下面的参数打印出来,方便自己联调
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 */
// 继续调用到java.c的JLI_Launch函数
return JLI_Launch(margc, margv, // 总参数(包括C参数和java参数)个数和对应的字符串数组
// java参数的个数和对应的字符串数组,在这里就是1 2 3
sizeof(const_jargs) / sizeof(char *), const_jargs,
// classpath的个数和对应的路径数组
sizeof(const_appclasspath) / sizeof(char *), const_appclasspath,
// FULL_VERSION 和 DOT_VERSION 分别表示jdk的版本的2种表现形式,看图2-2表示
FULL_VERSION,
DOT_VERSION,
// 程序名,这里指java
(const_progname != NULL) ? const_progname : *margv,
// 也是程序名,这里指java
(const_launcher != NULL) ? const_launcher : *margv,
// 有没有带java参数,有则为true,否则false
(const_jargs != NULL) ? JNI_TRUE : JNI_FALSE,
const_cpwildcard, const_javaw, const_ergo_class);
}
int
JLI_Launch(int argc, char ** argv, /* main argc, argc 主参数*/
int jargc, const char** jargv, /* java args java参数*/
int appclassc, const char** appclassv, /* app classpath */
const char* fullversion, /* full version defined */
const char* dotversion, /* dot version defined */
const char* pname, /* program name 这里都是java */
const char* lname, /* launcher name 这里都是java*/
jboolean javaargs, /* JAVA_ARGS */
jboolean cpwildcard, /* classpath wildcard*/
jboolean javaw, /* windows-only javaw */
jint ergo /* ergonomics class policy */
)
{
// 定义一些要用的变量
int mode = LM_UNKNOWN;
char *what = NULL;
char *cpath = 0;
char *main_class = NULL;
int ret;
InvocationFunctions ifn;
jlong start = 0, end = 0;
char jvmpath[MAXPATHLEN];
char jrepath[MAXPATHLEN];
char jvmcfg[MAXPATHLEN];
// 赋值
_fVersion = fullversion;
_dVersion = dotversion;
_launcher_name = lname;
_program_name = pname;
_is_java_args = javaargs;
_wc_enabled = cpwildcard;
_ergo_policy = ergo;
// 日志打印相关
InitLauncher(javaw);
DumpState();
if (JLI_IsTraceLauncher()) {
int i;
printf("Command line args:\n");
for (i = 0; i < argc ; i++) {
printf("argv[%d] = %s\n", i, argv[i]);
}
AddOption("-Dsun.java.launcher.diag=true", NULL);
}
// 选择正确的版本号,main_class是针对执行jar包时,Manifest中指定的main class
SelectVersion(argc, argv, &main_class);
/*
创建执行环境,主要做以下几件事
1、处理一下参数设置,比如按32位运行还64位运行
2、处理jrepath、jvmpath、jvmcfg(jvm的自身配置),后面都会细讲
3、设置执行程序路径:/xxx/jdk/bin/java
*/
CreateExecutionEnvironment(&argc, &argv,
jrepath, sizeof(jrepath),
jvmpath, sizeof(jvmpath),
jvmcfg, sizeof(jvmcfg));
if (!IsJavaArgs()) {
// 设置jvm自身使用的环境变量
SetJvmEnvironment(argc,argv);
}
// 创建JVM环境的函数,在linux中,jvm被编译为一个动态链接库libjvm.so(这个名字各系统可能不一样),这些函数都在这个库里
ifn.CreateJavaVM = 0;
// 获取默认Java虚拟机初始参数的函数
ifn.GetDefaultJavaVMInitArgs = 0;
// 记录开始时间
if (JLI_IsTraceLauncher()) {
start = CounterGet();
}
// 加载Java虚拟机,其实就是动态链接libjvm.so里面的几个函数(CreateJavaVM->JNI_CreateJavaVM、GetDefaultJavaVMInitArgs->JNI_GetDefaultJavaVMInitArgs、GetCreatedJavaVMs->JNI_GetCreatedJavaVMs),拿到函数指针,并由ifn来持有
if (!LoadJavaVM(jvmpath, &ifn)) {
return(6);
}
// 记录结束时间
if (JLI_IsTraceLauncher()) {
end = CounterGet();
}
// 日志打印
JLI_TraceLauncher("%ld micro seconds to LoadJavaVM\n",
(long)(jint)Counter2Micros(end-start));
++argv; // 参数后移
--argc; // 处理过的就减1
if (IsJavaArgs()) {
/* Preprocess wrapper arguments */
TranslateApplicationArgs(jargc, jargv, &argc, &argv);
if (!AddApplicationOptions(appclassc, appclassv)) {
return(1);
}
} else {
/* Set default CLASSPATH */
cpath = getenv("CLASSPATH");
if (cpath == NULL) {
cpath = ".";
}
SetClassPath(cpath);
}
/* 解析命令行中的option,并把解析出来的option都转成JavaVMOption对象存入JavaVMOption数组中
* 解析失败,程序直接退出
*/
if (!ParseArguments(&argc, &argv, &mode, &what, &ret, jrepath))
{
return(ret);
}
/* 如果有-jar option,那么就会覆盖-cp的classpath,所以这个时候classpath被覆盖了*/
if (mode == LM_JAR) {
SetClassPath(what); /* Override class path */
}
/* 设置jvm系统属性-Dsun.java.command */
SetJavaCommandLineProp(what, argc, argv);
/* 设置jvm系统属性-Dsun.java.launcher */
SetJavaLauncherProp();
/* 设置jvm平台相关的系统属性-Dsun.java.launcher.* */
SetJavaLauncherPlatformProps();
/*
这个函数才是正式开始执行程序的入口,主要做2件事
1、调用ShowSplashScreen函数展示Java虚拟机的欢迎画面
2、调用ContinueInNewThread函数创建一个新线程并在其中启动Java虚拟机,执行后续流程
*/
return JVMInit(&ifn, threadStackSize, argc, argv, mode, what, ret);
}