JVM启动过程源码分析

2019-06-15  本文已影响0人  快给我饭吃

从开始接触java写第一个hello world程序时,我们就知道,编译完成后只要在命令行里输入java yourClassFilename这个java命令,就能启动jvm虚拟机运行你的java程序。但是操作系统到底怎么启动jvm虚拟机的,那就要分析分析jdk的源码了。

启动步骤

从openjdk官网下载源码进行分析,jvm启动过程分为两部分:launch启动器和hotspot vm自身启动。大概步骤如下。

源码分析

c++程序的入口点,自然是唯一的main函数,main函数在openjdk/jdk/src/share/bin/main.c文件中,其调用了JLI_Launch启动,代码在openjdk/jdk/src/share/bin/java.c中。

main(int argc, char **argv)
{
    int margc;
    char** margv;
    const jboolean const_javaw = JNI_FALSE; //是否以javaw启动
    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);
}

JLI_Launch方法代码如下,整个jvm启动逻辑都在这里了。首先SelectVersion()方法确定系统安装了你指定的JRE版本,接着CreateExecutionEnvironment

/*
 * Entry point.
 */
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* pname,                      /* program name */
        const char* lname,                      /* launcher name */
        jboolean javaargs,                      /* JAVA_ARGS */
        jboolean cpwildcard,                    /* classpath wildcard*/
        jboolean javaw,                         /* windows-only javaw */
        jint ergo                               /* ergonomics class policy */
)
{
    //启动模式,如果非jar,非main class启动,那么程序将退出
    int mode = LM_UNKNOWN;
    //存储启动类名
    char *what = NULL;
    //储存classpath
    char *cpath = 0;
    //主类引用
    char *main_class = NULL;
    int ret;
    //加载JAVA_DLL时,将一些函数链接到此变量中
    InvocationFunctions ifn;
    jlong start, end;
    //jvm路径
    char jvmpath[MAXPATHLEN];
    //jre路径
    char jrepath[MAXPATHLEN];
    //jvm.cfg路径
    char jvmcfg[MAXPATHLEN];

    _fVersion = fullversion;
    _dVersion = dotversion;
    _launcher_name = lname;
    _program_name = pname;
    _is_java_args = javaargs;
    _wc_enabled = cpwildcard;
    _ergo_policy = ergo;

    //根据环境变量中的“JLDEBUG_ENV_ENTRY”参数来设置_launcher_debug,表示是否打印调试信息
    /*
     * Initialize platform specific settings
     */
    InitLauncher(javaw);
    //printf一些调试的提示,无其他作用
    DumpState();
    //判断_launcher_debug输出调试信息
    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);
    }

    /*
     * Make sure the specified version of the JRE is running.
     *
     * There are three things to note about the SelectVersion() routine:
     *  1) If the version running isn't correct, this routine doesn't
     *     return (either the correct version has been exec'd or an error
     *     was issued).
     *  2) Argc and Argv in this scope are *not* altered by this routine.
     *     It is the responsibility of subsequent code to ignore the
     *     arguments handled by this routine.
     *  3) As a side-effect, the variable "main_class" is guaranteed to
     *     be set (if it should ever be set).  This isn't exactly the
     *     poster child for structured programming, but it is a small
     *     price to pay for not processing a jar file operand twice.
     *     (Note: This side effect has been disabled.  See comment on
     *     bugid 5030265 below.);ll;;
     */
    // jre相关,从命令行-version:或从Mainfest获取jre版本,并使用LocateJRE()从注册表键值JavaHome定位到jre路径,最后使用ExecJRE()小启动一把jre
    SelectVersion(argc, argv, &main_class);

    //准备环境,先根据java.exe目录查找jdk的jre路径,称为private jre,取不到则取注册表的jre路径,称为public jre。然后查找到jre/bin/java.dll为java虚拟机。查找jre/lib/amd64/jvm.cfg文件,获取虚拟机启动类型。根据指定虚拟机类型如server,查找jre/bin/server/jvm.dll虚拟机文件。
    CreateExecutionEnvironment(&argc, &argv,
                               jrepath, sizeof(jrepath),
                               jvmpath, sizeof(jvmpath),
                               jvmcfg,  sizeof(jvmcfg));

    ifn.CreateJavaVM = 0;
    ifn.GetDefaultJavaVMInitArgs = 0;

    if (JLI_IsTraceLauncher()) {
        start = CounterGet();
    }
    //加载jvm.dll到内存,并将动态库中的函数链接至ifn.CreateJavaVM,ifn.GetDefaultJavaVMInitArgs
    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;

    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 = ".";
        }

        //设置CLASSPATH,并设置虚拟机参数Xms, Xmx, Xss
        SetClassPath(cpath);
    }

    /* Parse command line options; if the return value of
     * ParseArguments is false, the program should exit.
     */
    //解析所有option参数,并将其存到JavaVMOption *options变量中
    if (!ParseArguments(&argc, &argv, &mode, &what, &ret, jrepath))
    {
        return(ret);
    }

    /* Override class path if -jar flag was specified */
    if (mode == LM_JAR) {
        SetClassPath(what);     /* Override class path */
    }

    /* set the -Dsun.java.command pseudo property */
    SetJavaCommandLineProp(what, argc, argv);

    /* Set the -Dsun.java.launcher pseudo property */
    SetJavaLauncherProp();

    /* set the -Dsun.java.launcher.* platform properties */
    SetJavaLauncherPlatformProps();
    
    //展示splash screen,开启新线程去初始化JVM,最终调用jvm.dll中的CreateJavaVM启动虚拟机。并通过LoadMainClass()方法获取main class,找到bootstrap类加载器加载sun/launcher/LauncherHelper,执行该类的checkAndLoadMain方法,初始化ExtClassLoader和AppClassLoader类加载器,执行sun.misc.Launcher.getLauncher()的getClassLoader()获取AppClassLoader加载主类,调用main class的main方法
    return JVMInit(&ifn, threadStackSize, argc, argv, mode, what, ret);
}

总结的不是很好,有空我再多写几篇。

上一篇下一篇

猜你喜欢

热点阅读