Java Hotspot虚拟机的启动过程(一)

本文介绍Hotspot虚拟机是如何从命令行启动的,下面从常用的java命令入手。

java命令

java可执行文件是JDK工具中的一员,这些工具的具体用法可以参考JDK Development Tools。jdk/make/CompileLaunchers.gmk这个Makefile文件指定了各工具的文件名和编译时用到的宏定义、编译选项等,下面是编译javac命令用到的部分Makefile:

$(eval $(call SetupLauncher,javac, \
    -DEXPAND_CLASSPATH_WILDCARDS \
    -DNEVER_ACT_AS_SERVER_CLASS_MACHINE \
    -DJAVA_ARGS='{ "-J-ms8m"$(COMMA) "com.sun.tools.javac.Main"$(COMMA) }'))

SetupLauncher是CompileLaunchers.gmk中的函数,第一个参数是文件名javac,第二个参数是额外的CFLAGS。所以在编译javac时编译器定义了宏JAVA_ARGS的值是{ "-J-ms8m", "com.sun.tools.javac.Main", },生成的可执行文件名是javac。
JDK工具的main函数均定义在文件jdk/src/share/bin/main.c中,代码如下,其用到的defines.h头文件也在下面列出。

/*
 * This file contains the main entry point into the launcher code
 * this is the only file which will be repeatedly compiled by other
 * tools. The rest of the files will be linked in.
 */
#include "defines.h"

int
main(int argc, char **argv)
{
    int margc;
    char** margv;
    const jboolean const_javaw = JNI_FALSE;
    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);
}

defines.h头文件:

#ifndef _DEFINES_H
#define _DEFINES_H

#include "java.h"

/*
 * This file contains commonly defined constants used only by main.c
 * and should not be included by another file.
 */
#ifndef FULL_VERSION
/* make sure the compilation fails */
#error "FULL_VERSION must be defined"
#endif

#if defined(JDK_MAJOR_VERSION) && defined(JDK_MINOR_VERSION)
#define DOT_VERSION JDK_MAJOR_VERSION "." JDK_MINOR_VERSION
#else
/* make sure the compilation fails */
#error "JDK_MAJOR_VERSION and JDK_MINOR_VERSION must be defined"
#endif


#ifdef JAVA_ARGS
static const char* const_progname = "java";
static const char* const_jargs[] = JAVA_ARGS;
/*
 * ApplicationHome is prepended to each of these entries; the resulting
 * strings are concatenated (separated by PATH_SEPARATOR) and used as the
 * value of -cp option to the launcher.
 */
#ifndef APP_CLASSPATH
#define APP_CLASSPATH        { "/lib/tools.jar", "/classes" }
#endif /* APP_CLASSPATH */
static const char* const_appclasspath[] = APP_CLASSPATH;
#else  /* !JAVA_ARGS */
#ifdef PROGNAME
static const char* const_progname = PROGNAME;
#else
static char* const_progname = NULL;
#endif
static const char** const_jargs = NULL;
static const char** const_appclasspath = NULL;
#endif /* JAVA_ARGS */

#ifdef LAUNCHER_NAME
static const char* const_launcher = LAUNCHER_NAME;
#else  /* LAUNCHER_NAME */
static char* const_launcher = NULL;
#endif /* LAUNCHER_NAME */

#ifdef EXPAND_CLASSPATH_WILDCARDS
static const jboolean const_cpwildcard = JNI_TRUE;
#else
static const jboolean const_cpwildcard = JNI_FALSE;
#endif /* EXPAND_CLASSPATH_WILDCARDS */

#if defined(NEVER_ACT_AS_SERVER_CLASS_MACHINE)
static const jint const_ergo_class = NEVER_SERVER_CLASS;
#elif defined(ALWAYS_ACT_AS_SERVER_CLASS_MACHINE)
static const jint const_ergo_class = ALWAYS_SERVER_CLASS;
#else
static const jint const_ergo_class = DEFAULT_POLICY;
#endif /* NEVER_ACT_AS_SERVER_CLASS_MACHINE */

#endif /*_DEFINES_H */

在头文件中若JAVA_ARGS宏有定义,则const_jargs指针数组的值不为NULL,而是编译时传递的宏,IsJavaArgs()函数返回JAVA_ARGS宏是否被定义,其他宏同理。

JLI_Launch函数

JLI_Launch函数定义在文件jdk/src/share/bin/java.c中,该文件起始部分的注释如下所示:

/*
 * Shared source for 'java' command line tool.
 *
 * If JAVA_ARGS is defined, then acts as a launcher for applications. For
 * instance, the JDK command line tools such as javac and javadoc (see
 * makefiles for more details) are built with this program.  Any arguments
 * prefixed with '-J' will be passed directly to the 'java' command.
 */

从注释可知,如果宏JAVA_ARGS有定义,那么JDK工具是一个Launcher的角色,作用如下:

  • 去除虚拟机不处理的命令行选项,包括-client、-server和数据模型选项(-d32和-d64)等,注意这些是虚拟机不处理的选项但是JDK工具依然会根据这些选项创建符合选项的虚拟机;
  • 对部分命令行选项做转换,去除选项开头的-J,如"-J-foo"选项会被转换成"-foo"选项传给虚拟机。

JLI_Launch函数的代码如下所示:

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 */
)
{
    int mode = LM_UNKNOWN;
    char *what = NULL;
    char *cpath = 0;
    char *main_class = NULL;
    int ret;
    InvocationFunctions ifn;
    jlong start, end;
    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);
    }

    SelectVersion(argc, argv, &main_class);

    CreateExecutionEnvironment(&argc, &argv,
                               jrepath, sizeof(jrepath),
                               jvmpath, sizeof(jvmpath),
                               jvmcfg,  sizeof(jvmcfg));

    if (!IsJavaArgs()) {
        SetJvmEnvironment(argc,argv);
    }

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

    if (JLI_IsTraceLauncher()) {
        start = CounterGet();
    }

    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 = ".";
        }
        SetClassPath(cpath);
    }

    /* Parse command line options; if the return value of
     * ParseArguments is false, the program should exit.
     */
    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();

    return JVMInit(&ifn, threadStackSize, argc, argv, mode, what, ret);
}

简言之,JLI_Launch函数主要做了以下事情:

  1. 将参数保存到全局变量,如版本号、文件名、是否定义了java参数(即JAVA_ARGS宏是否定义)等;
  2. InitLauncher函数设置调试开关,如果环境变量_JAVA_LAUNCHER_DEBUG有定义则开启Launcher的调试模式,后续调用JLI_IsTraceLauncher函数会返回真(即值1);
  3. SelectVersion函数解析-version:release、-jre-restrict-search、-no-jre-restrict-search和-splash:imgname 选项,确保运行适当版本的JRE(注意不要将JRE的版本与JVM的数据模型搞混)。需要的JRE版本既可以从命令行选项-version:release指定,也可以在jar包的META-INF/MANIFEST.MF文件中用JRE-Version键指定;
  4. CreateExecutionEnvironment函数为后续创建虚拟机选择了数据模型,去除-d32、-J-d32、-d64和-J-d64选项;
  5. LoadJavaVM函数从JVM动态库获取函数指针;
  6. 解析其他命令行选项;
  7. 设置额外的虚拟机启动选项;
  8. 初始化JVM。

CreateExecutionEnvironment函数

CreateExecutionEnvironment函数定义在jdk/src/solaris/bin/java_md_solinux.c文件中,分析它之前先看JDK和JRE的安装目录结构和一些辅助函数。

安装目录结构

Cent OS 7上JDK和JRE的安装目录结构如下图所示,下文分析时会用到:

/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.191.b12-1.el7_6.x86_64
                                                |- bin
                                                |   |- jar
                                                |   |- java
                                                |   |- javac
                                                |   |- ...
                                                |- jre
                                                |   |- bin
                                                |   |   |- java
                                                |   |   |- keytool
                                                |   |   |- ...
                                                |   |- lib
                                                |   |   |- amd64
                                                |   |   |   |- jvm.cfg
                                                |   |   |   |- client
                                                |   |   |   |- server
                                                |   |   |   |   |- libjvm.so
                                                |   |   |   |   |- Xusage.txt
                                                |   |   |   |- libjava.so
                                                |   |   |   |- libnio.so
                                                |   |   |   |- ...
                                                |   |   |- ext
                                                |- lib
                                                |   |- amd64
                                                |   |   |- libjawt.so
                                                |- tapset
                                                |- include
                                                

GetJREPath函数

GetJREPath函数查找JRE路径,将路径保存到参数path指向的地址,其代码如下所示:

static jboolean
GetJREPath(char *path, jint pathsize, const char * arch, jboolean speculative)
{
    char libjava[MAXPATHLEN];

    if (GetApplicationHome(path, pathsize)) {
        /* Is JRE co-located with the application? */
        JLI_Snprintf(libjava, sizeof(libjava), "%s/lib/%s/" JAVA_DLL, path, arch);
        if (access(libjava, F_OK) == 0) {
            JLI_TraceLauncher("JRE path is %s\n", path);
            return JNI_TRUE;
        }
        /* ensure storage for path + /jre + NULL */
        if ((JLI_StrLen(path) + 4  + 1) > pathsize) {
            JLI_TraceLauncher("Insufficient space to store JRE path\n");
            return JNI_FALSE;
        }
        /* Does the app ship a private JRE in /jre directory? */
        JLI_Snprintf(libjava, sizeof(libjava), "%s/jre/lib/%s/" JAVA_DLL, path, arch);
        if (access(libjava, F_OK) == 0) {
            JLI_StrCat(path, "/jre");
            JLI_TraceLauncher("JRE path is %s\n", path);
            return JNI_TRUE;
        }
    }

    if (!speculative)
      JLI_ReportErrorMessage(JRE_ERROR8 JAVA_DLL);
    return JNI_FALSE;
}
  • 参数path指向JRE路径字符串,参数arch表示先前获取的处理器架构如sparc9和amd64等;
  • GetApplicationHome函数得到可执行文件(如java)所在的应用目录(可执行文件所在bin目录的父目录),如/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.191.b12-1.el7_6.x86_64/jre/bin/java的应用目录是/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.191.b12-1.el7_6.x86_64/jre
  • libjava字符数组保存JRE动态库的绝对路径,JRE动态库的文件名由JAVA_DLL宏定义,在Windows下是java.dll,在Linux下是libjava.so,Mac OS X下是libjava.dylib;
  • 如果JRE动态库文件应用目录/lib/处理器架构/libjava.so存在,那么应用目录就是JRE目录;否则如果文件应用目录/jre/lib/处理器架构/libjava.so存在,那么应用目录/jre就是JRE目录;如果以上两处都没有JRE动态库文件则报错。

ReadKnownVMs函数

ReadKnownVMs函数读取JVM配置文件jvm.cfg,其路径是JRE目录/lib/处理器架构/jvm.cfg。这个配置文件每行的含义是如果某一标识符后面是KNOWN,那么与该标识符同名的目录里包含JVM动态库,以下述文件内容为例,server一行是KNOWN,那么JRE目录/lib/处理器架构/server/libjvm.so是一个JVM动态库。

-server KNOWN
-client IGNORE

CheckJvmType函数

CheckJvmType函数检查命令行选项指定的JVM类型是否符合要求,返回默认或指定的JVM类型(即JVM配置文件某一行的标识符)。

GetJVMPath函数

GetJVMPath函数根据CheckJvmType函数返回的JVM类型查找JVM动态库,JVM动态库的绝对路径就是JRE目录/lib/处理器架构/JVM类型/libjvm.so

RequiresSetenv函数

RequiresSetenv函数检查是否需要设置环境变量,其代码如下所示:

static jboolean
RequiresSetenv(int wanted, const char *jvmpath) {
    char jpath[PATH_MAX + 1];
    char *llp;
    char *dmllp = NULL;
    char *p; /* a utility pointer */

    llp = getenv("LD_LIBRARY_PATH");
    /* no environment variable is a good environment variable */
    if (llp == NULL && dmllp == NULL) {
        return JNI_FALSE;
    }
    if ((getgid() != getegid()) || (getuid() != geteuid())) {
        return JNI_FALSE;
    }

    /*
     * Prevent recursions. Since LD_LIBRARY_PATH is the one which will be set by
     * previous versions of the JRE, thus it is the only path that matters here.
     * So we check to see if the desired JRE is set.
     */
    JLI_StrNCpy(jpath, jvmpath, PATH_MAX);
    p = JLI_StrRChr(jpath, '/');
    *p = '\0';
    if (llp != NULL && JLI_StrNCmp(llp, jpath, JLI_StrLen(jpath)) == 0) {
        return JNI_FALSE;
    }

    /* scrutinize all the paths further */
    if (llp != NULL &&  ContainsLibJVM(wanted, llp)) {
        return JNI_TRUE;
    }
    if (dmllp != NULL && ContainsLibJVM(wanted, dmllp)) {
        return JNI_TRUE;
    }
    return JNI_FALSE;
}
  • 若环境变量LD_LIBRARY_PATH没有被设置,那么返回0;
  • 若uid与euid不同或gid与egid不同,那么返回0;
  • 若$LD_LIBRARY_PATH的开头和JVM动态库所在目录相同则返回0;
  • 若$LD_LIBRARY_PATH中有JVM动态库则返回1;
  • 其他情况均返回0。

了解了RequiresSetenv函数的内部执行过程后就可以构造返回1的条件:将jdk安装目录拷贝到home目录下,记为private,然后在bash里运行LD_LIBRARY_PATH=~/private/jre/lib/amd64/server _JAVA_LAUNCHER_DEBUG=1 java -version即可使返回值为1。

CreateExecutionEnvironment函数

回到CreateExecutionEnvironment函数,其代码如下所示:

void
CreateExecutionEnvironment(int *pargc, char ***pargv,
                        char jrepath[], jint so_jrepath,
                        char jvmpath[], jint so_jvmpath,
                        char jvmcfg[],  jint so_jvmcfg) {
    jboolean jvmpathExists;
    /* Compute/set the name of the executable */
    SetExecname(*pargv);
    /* Check data model flags, and exec process, if needed */
    {
        char *arch        = (char *)GetArch(); /* like sparc or sparcv9 */
        char * jvmtype    = NULL;
        int  argc         = *pargc;
        char **argv       = *pargv;
        int running       = CURRENT_DATA_MODEL;
        int wanted        = running;
        jboolean mustsetenv = JNI_FALSE;
        char *runpath     = NULL; /* existing effective LD_LIBRARY_PATH setting */
        char* new_runpath = NULL; /* desired new LD_LIBRARY_PATH string */
        char* newpath     = NULL; /* path on new LD_LIBRARY_PATH */
        char* lastslash   = NULL;
        char** newenvp    = NULL; /* current environment */

        char** newargv    = NULL;
        int    newargc    = 0;

        /*
        * Starting in 1.5, all unix platforms accept the -d32 and -d64
        * options.  On platforms where only one data-model is supported
        * (e.g. ia-64 Linux), using the flag for the other data model is
        * an error and will terminate the program.
        */
        { /* open new scope to declare local variables */
            int i;
            newargv = (char **)JLI_MemAlloc((argc+1) * sizeof(char*));
            newargv[newargc++] = argv[0];

            /* scan for data model arguments and remove from argument list;
            last occurrence determines desired data model */
            for (i=1; i < argc; i++) {

                if (JLI_StrCmp(argv[i], "-J-d64") == 0 || JLI_StrCmp(argv[i], "-d64") == 0) {
                    wanted = 64;
                    continue;
                }
                if (JLI_StrCmp(argv[i], "-J-d32") == 0 || JLI_StrCmp(argv[i], "-d32") == 0) {
                    wanted = 32;
                    continue;
                }
                newargv[newargc++] = argv[i];

                if (IsJavaArgs()) {
                    if (argv[i][0] != '-') continue;
                } else {
                    if (JLI_StrCmp(argv[i], "-classpath") == 0 || JLI_StrCmp(argv[i], "-cp") == 0) {
                        i++;
                        if (i >= argc) break;
                        newargv[newargc++] = argv[i];
                        continue;
                    }
                    if (argv[i][0] != '-') { i++; break; }
                }
            }
            /* copy rest of args [i .. argc) */
            while (i < argc) {
                newargv[newargc++] = argv[i++];
            }
            newargv[newargc] = NULL;
            argc = newargc;
            argv = newargv;
        }
        /* If the data model is not changing, it is an error if the
            jvmpath does not exist */
        if (wanted == running) {
            /* Find out where the JRE is that we will be using. */
            if (!GetJREPath(jrepath, so_jrepath, arch, JNI_FALSE) ) {
                JLI_ReportErrorMessage(JRE_ERROR1);
                exit(2);
            }
            JLI_Snprintf(jvmcfg, so_jvmcfg, "%s%slib%s%s%sjvm.cfg",
                        jrepath, FILESEP, FILESEP,  arch, FILESEP);
            /* Find the specified JVM type */
            if (ReadKnownVMs(jvmcfg, JNI_FALSE) < 1) {
                JLI_ReportErrorMessage(CFG_ERROR7);
                exit(1);
            }

            jvmpath[0] = '\0';
            jvmtype = CheckJvmType(pargc, pargv, JNI_FALSE);
            if (JLI_StrCmp(jvmtype, "ERROR") == 0) {
                JLI_ReportErrorMessage(CFG_ERROR9);
                exit(4);
            }

            if (!GetJVMPath(jrepath, jvmtype, jvmpath, so_jvmpath, arch, 0 )) {
                JLI_ReportErrorMessage(CFG_ERROR8, jvmtype, jvmpath);
                exit(4);
            }
            /*
            * we seem to have everything we need, so without further ado
            * we return back, otherwise proceed to set the environment.
            */
            mustsetenv = RequiresSetenv(wanted, jvmpath);
            JLI_TraceLauncher("mustsetenv: %s\n", mustsetenv ? "TRUE" : "FALSE");

            if (mustsetenv == JNI_FALSE) {
                JLI_MemFree(newargv);
                return;
            }
        } else {  /* do the same speculatively or exit */
            JLI_ReportErrorMessage(JRE_ERROR2, wanted);
            exit(1);
        }
        if (mustsetenv) {
            runpath = getenv(LD_LIBRARY_PATH);
            jvmpath = JLI_StringDup(jvmpath);
            size_t new_runpath_size = ((runpath != NULL) ? JLI_StrLen(runpath) : 0) +
                    2 * JLI_StrLen(jrepath) + 2 * JLI_StrLen(arch) +
                    JLI_StrLen(jvmpath) + 52;
            new_runpath = JLI_MemAlloc(new_runpath_size);
            newpath = new_runpath + JLI_StrLen(LD_LIBRARY_PATH "=");

            {
                /* remove the name of the .so from the JVM path */
                lastslash = JLI_StrRChr(jvmpath, '/');
                if (lastslash)
                    *lastslash = '\0';
                sprintf(new_runpath, LD_LIBRARY_PATH "="
                        "%s:"
                        "%s/lib/%s:"
                        "%s/../lib/%s",
                        jvmpath,
                        jrepath, arch,
                        jrepath, arch
                );
                if (runpath != NULL &&
                        JLI_StrNCmp(newpath, runpath, JLI_StrLen(newpath)) == 0 &&
                        (runpath[JLI_StrLen(newpath)] == 0 || runpath[JLI_StrLen(newpath)] == ':') &&
                        (running == wanted) /* data model does not have to be changed */
                ) {
                    JLI_MemFree(newargv);
                    JLI_MemFree(new_runpath);
                    return;
                }
            }

            if (runpath != 0) {
                /* ensure storage for runpath + colon + NULL */
                if ((JLI_StrLen(runpath) + 1 + 1) > new_runpath_size) {
                    JLI_ReportErrorMessageSys(JRE_ERROR11);
                    exit(1);
                }
                JLI_StrCat(new_runpath, ":");
                JLI_StrCat(new_runpath, runpath);
            }

            if (putenv(new_runpath) != 0) {
                exit(1); /* problem allocating memory; LD_LIBRARY_PATH not set properly */
            }
            /*
             * Unix systems document that they look at LD_LIBRARY_PATH only
             * once at startup, so we have to re-exec the current executable
             * to get the changed environment variable to have an effect.
             */
            newenvp = environ;
        }
        {
            char *newexec = execname;
            JLI_TraceLauncher("TRACER_MARKER:About to EXEC\n");
            (void) fflush(stdout);
            (void) fflush(stderr);
            if (mustsetenv) {
                execve(newexec, argv, newenvp);
            } else {
                execv(newexec, argv);
            }
            JLI_ReportErrorMessageSys(JRE_ERROR4, newexec);
        }
        exit(1);
    }
}

它主要做了以下几件事:

  • SetExecname函数使用readlink函数和/proc/self/exe将可执行文件(如java、javac等命令)的绝对路径如/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.191.b12-1.el7_6.x86_64/jre/bin/java保存到全局变量execname中,后面查找JRE路径时会用到;
  • 变量running表示当前机器的数据模型,其值是CURRENT_DATA_MODEL,这个宏定义在文件jdk/src/share/bin/java.h中,其值是 (CHAR_BIT * sizeof(void*)),CHAR_BIT是定义在limits.h中的宏,表示一个char类型的比特数,sizeof(void*)则是void类型指针的大小;
  • 变量wanted表示命令行选项中需要的数据模型,-J-d64和-d64指定64位数据模型,-J-d32和-d32则指定32位数据模型;
  • 如果命令行选项中需要的数据模型与当前运行的虚拟机的数据模型相同,那么查找JRE、读取JVM配置文件并查找JVM动态库,如果有任何一项出错则报错;
  • RequiresSetenv函数测试是否需要设置环境变量,若不需要则结束,命令行选项中的-d64和-d32会在后续ParseArguments函数中被过滤;否则意味着LD_LIBRARY_PATH内有其他的JVM动态库,但它们并不是我们想用的,所以为LD_LIBRARY_PATH环境变量设置新值为GetJVMPath函数返回的JVM动态库所在目录:JRE目录/lib/处理器架构:JRE目录/../lib/处理器架构:$LD_LIBRARY_PATH,接着使用execve函数和新的LD_LIBRARY_PATH环境变量执行原可执行文件(execv那个else分支感觉到不了),但命令行选项去除了-J-d64、-d64、-J-d32和-d32。

LoadJavaVM函数

LoadJavaVM函数从CreateExecutionEnvironment函数返回的JVM动态库中使用dlopen和dlsym库函数查找JNI_CreateJavaVM、JNI_GetDefaultJavaVMInitArgs和JNI_GetCreatedJavaVMs函数指针并保存到InvocationFunctions结构体中。

jboolean
LoadJavaVM(const char *jvmpath, InvocationFunctions *ifn)
{
    void *libjvm;

    JLI_TraceLauncher("JVM path is %s\n", jvmpath);

    libjvm = dlopen(jvmpath, RTLD_NOW + RTLD_GLOBAL);
    if (libjvm == NULL) {
        JLI_ReportErrorMessage(DLL_ERROR1, __LINE__);
        JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());
        return JNI_FALSE;
    }

    ifn->CreateJavaVM = (CreateJavaVM_t)
        dlsym(libjvm, "JNI_CreateJavaVM");
    if (ifn->CreateJavaVM == NULL) {
        JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());
        return JNI_FALSE;
    }

    ifn->GetDefaultJavaVMInitArgs = (GetDefaultJavaVMInitArgs_t)
        dlsym(libjvm, "JNI_GetDefaultJavaVMInitArgs");
    if (ifn->GetDefaultJavaVMInitArgs == NULL) {
        JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());
        return JNI_FALSE;
    }

    ifn->GetCreatedJavaVMs = (GetCreatedJavaVMs_t)
        dlsym(libjvm, "JNI_GetCreatedJavaVMs");
    if (ifn->GetCreatedJavaVMs == NULL) {
        JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());
        return JNI_FALSE;
    }

    return JNI_TRUE;
}

我理解的设置新环境变量后再exec的原因是使后续从LD_LIBRARY_PATH查找到的JVM动态库与即将运行的JVM相符。

其余函数的分析请见下一篇文章。

你可能感兴趣的:(Java Hotspot虚拟机的启动过程(一))