Hotspot源码解析-第二章

发布时间:2023年12月22日

第二章

2.1 执行过程

? 我们先从执行一个class文件开始,通过命令java [options] xxx.class param1 param2 ... paramn来执行一个java程序,在linux操作系统下的shell环境,执行一条命令时,shell会先fork一个新的进程来执行命令,一般根据规范程序的执行入口是main方法,jvm是c/c++实现的,这样我们只要找到该程序的main函数就行,通过查找得知main.c里面的main函数就是入口

2.1.1 main.c

2.1.1.1 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);
}

图2-1
在这里插入图片描述

图2-2
在这里插入图片描述

2.1.2 java.c

2.1.2.1 JLI_Launch
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);
}
文章来源:https://blog.csdn.net/zhang527294844/article/details/135158207
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。