JVM 1.3 JVM 启动流程

JVM是Java程序运行的环境,但是他同时一个操作系统的一个应用程序一个进程,因此他也有他自己的运行的生命周期,也有自己的代码和数据空间。

JVM工作原理和特点主要是指操作系统装入JVM是通过JDK中java.exe来完成,通过下面5步来完成JVM环境.

目的
备注
备注
1.Java xxx
2.装载配置
根据当前路径和系统的版本寻找jvm.cfg
3.根据配置寻找jvm.dll
4.初始化JVM获得JNIEnv接口
5.找到main方法
jvm.dll为jvm主要实现
JNIEnv为JVM接口,findClass等操作通过他实现
1.步骤1:JVM装入环境

JVM提供的方式是操作系统的动态链接库文件(dll文件)

1)JVM路径的确定

Java是通过GetApplicationHomeAPI来获得当前的Java.exe绝对路径:

该方法实现如下:

/*
 * 如果一个应用服务的执行路径是:"c:\foo\bin\javac", 那么就会缓存"c:\foo"目录
 */
jboolean
GetApplicationHome(char *buf, jint bufsize)// buf 相当于 "c:\foo\bin\javac"
{
    char *cp;
    GetModuleFileName(0, buf, bufsize);
    *JLI_StrRChr(buf, '\\') = '\0'; /* 移除路径中的.exe文件名*/
    if ((cp = JLI_StrRChr(buf, '\\')) == 0) {
        /* 当应用使用的是根目录而不包含bin\ 目录. */
        buf[0] = '\0';
        return JNI_FALSE;
    }
    *cp = '\0';  /* 移除目录中的 bin\ 部分 */
    return JNI_TRUE;
}

再看看其调用者:

/*
 * 根据当前任性的exe命令文件位置或者注册设置来找到JRE的根目录位置*/
jboolean
GetJREPath(char *path, jint pathsize){
    char javadll[MAXPATHLEN];
    struct stat s;

    if (GetApplicationHome(path, pathsize)) {
        /* Is JRE co-located with the application? */
        JLI_Snprintf(javadll, sizeof(javadll), "%s\\bin\\" JAVA_DLL, path);
        if (stat(javadll, &s) == 0) {
            JLI_TraceLauncher("JRE path is %s\n", path);
            return JNI_TRUE;
        }
        /* 确保存储路径 + \jre + NULL */
        if ((JLI_StrLen(path) + 4 + 1) > pathsize) {
            JLI_TraceLauncher("Insufficient space to store JRE path\n");
            return JNI_FALSE;
        }
        /* 此应用程序是否在 应用程序家目录($JAVA_HOME)下有jre目录 */
        JLI_Snprintf(javadll, sizeof (javadll), "%s\\jre\\bin\\" JAVA_DLL, path);
        if (stat(javadll, &s) == 0) { 
            JLI_StrCat(path, "\\jre"); //如果有jre目录,那么将把$JAVA_HOME/jre作为jre根目录
            JLI_TraceLauncher("JRE path is %s\n", path);
            return JNI_TRUE;
        }
    }

显然,如果我的指令命令java.exe所在路径是C:\jdk8\bin\java.exe,那么那会去掉bin\java.exe,然后进行各项判断,最终找到C:\jdk8\bin\jre作为JRE的家目录。如果没有找到的话,最终会调用GetPublicJREHome API 到你的注册表中HKEY_LOCAL_MACHINE\Software\JavaSoft\Java Runtime Environment\当前版本号\JAVA_HOME去找的。

2)装载jvm.cfg文件

然后装载JVM.cfg文件:JRE路径\lib\ARCH(CPU构架)\JVM.cfg :

关于ARCH(CPU构架)的判断则是通过如下方法得到的:

/*
 * Returns the arch path, to get the current arch use the
 * macro GetArch, nbits here is ignored for now.
 */
const char *
GetArchPath(int nbits)
{
#ifdef _M_AMD64    // 貌似无论是AMDCPU还是现在普遍使用的英特尔处理器都是这个目录
    return "amd64";
#elif defined(_M_IA64) // 英特尔安腾64位处理器
    return "ia64";
#else
    return "i386";  //32位处理器
#endif
}

例如我的电脑是Intel Core 8870U 64位的,那么目录是amd64,也因此我的jvm.cfg文件的路径是:

C:\jdk8\jre\lib\amd64\jvm.cfg,其内容如下:

-server KNOWN  # 默认第一选项即server类型 VM
-client IGNORE # IGNORE即忽略该模式,使用默认模式

值得注意的是,好像在后续的版本中,默认使用的都是server类型的vm了,甚至连之前默认的client版本的vm都不再支持了。

一般来说,默认的java.dll文件存储位置是在KaTeX parse error: Expected 'EOF', got '\jdk' at position 27: …n/VM类型,例如我的:`C:\̲j̲d̲k̲8\jre\bin\serve…JRE_HOME/bin下面并没有类似client等字眼,即不支持client vm了。

要知道,官方之所以选择server vm,而放弃client vm主要还是因为client vm是轻量级的,启动时速度比较快,但是稳定后,其执行速度远不及server vm,而sever vm虽然是重量级的,但是其内部的优化做的比较好,所以一旦稳定下来,server vm可以吊打 client vm。

2.装载jvm.dll文件

通过第一步已经找到了JVM的路径,Java通过LoadJavaVM来装入JVM.dll文件.装入工作很简单就是调用Windows API函数:

/*
 * 从JRE路径中加载一个jvm,并初始化调用函数
 */
jboolean
LoadJavaVM(const char *jvmpath, InvocationFunctions *ifn)
{
    HINSTANCE handle;

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

    /*
     * 首选需要加载C运行时库,副本是假定存在于“jre path”目录中。如果找不到这里(或者“jre路径”解析失	  * 败),跳过显式加载并按照默认处理方式进行处理,这很可能是执行失败。
     */
    LoadMSVCRT();

    /* 加载jvm.dll动态链接库文件 */
    if ((handle = LoadLibrary(jvmpath)) == 0) {
        JLI_ReportErrorMessage(DLL_ERROR4, (char *)jvmpath);
        return JNI_FALSE;
    }

    /* 现在获取这个函数的地址信息 */
    ifn->CreateJavaVM =
        (void *)GetProcAddress(handle, "JNI_CreateJavaVM");
    ifn->GetDefaultJavaVMInitArgs =
        (void *)GetProcAddress(handle, "JNI_GetDefaultJavaVMInitArgs");
    if (ifn->CreateJavaVM == 0 || ifn->GetDefaultJavaVMInitArgs == 0) {
        JLI_ReportErrorMessage(JNI_ERROR1, (char *)jvmpath);
        return JNI_FALSE;
    }

    return JNI_TRUE;
}

LoadLibrary装载JVM.dll动态连接库.然后把JVM.dll中的导出函数JNI_CreateJavaVM和JNI_GetDefaultJavaVMInitArgs挂接到InvocationFunctions变量的CreateJavaVM和GetDefaultJavaVMInitArgs函数指针变量上。JVM.dll的装载工作宣告完成。

3.初始化JVM

获得本地调用接口,这样就可以在Java中调用JVM的函数了.调用InvocationFunctions->CreateJavaVM也就是JVM中JNI_CreateJavaVM方法获得JNIEnv结构的实例.

4.运行程序

Java程序有两种方式:

​ 一种是jar包

​ 一种是class. 运行jar

Java -jar XXX.jar运行的时候,Java.exe调用GetApplicationClass函数,该函数先获得JNIEnv实例然后调用Java类Java.util.jar.JarFileJNIEnv中方法getManifest()并从返回的Manifest对象中取getAttributes(“Main-Class”)的值即jar包中文件:META-INF/MANIFEST.MF指定的Main-Class的主类名作为运行的主类。之后main函数会调用Java.c中LoadMainClass方法装载该主类(使用JNIEnv实例的FindClass)。main函数直接调用Java.c中LoadMainClass方法装载该类。

1)应用程序的主类的获取
static jclass
GetApplicationClass(JNIEnv *env)
{
    jmethodID mid;
    jobject result;
    jclass cls = GetLauncherHelperClass(env);
    NULL_CHECK0(cls);
    NULL_CHECK0(mid = (*env)->GetStaticMethodID(env, cls,
                "getApplicationClass",
                "()Ljava/lang/Class;"));

    return (*env)->CallStaticObjectMethod(env, cls, mid);
}
2)应用程序主类main方法的参数的获取:
/*
 * For tools, convert command line args thus:
 *   javac -cp foo:foo/"*" -J-ms32m ...
 *   java -ms32m -cp JLI_WildcardExpandClasspath(foo:foo/"*") ...
 *
 * Takes 4 parameters, and returns the populated arguments
 */
static void
TranslateApplicationArgs(int jargc, const char **jargv, int *pargc, char ***pargv)
{
    int argc = *pargc;
    char **argv = *pargv;
    int nargc = argc + jargc;
    char **nargv = JLI_MemAlloc((nargc + 1) * sizeof(char *));
    int i;

    *pargc = nargc;
    *pargv = nargv;

    /* Copy the VM arguments (i.e. prefixed with -J) */
    for (i = 0; i < jargc; i++) {
        const char *arg = jargv[i];
        if (arg[0] == '-' && arg[1] == 'J') {
            *nargv++ = ((arg + 2) == NULL) ? NULL : JLI_StringDup(arg + 2);
        }
    }

    for (i = 0; i < argc; i++) {
        char *arg = argv[i];
        if (arg[0] == '-' && arg[1] == 'J') {
            if (arg[2] == '\0') {
                JLI_ReportErrorMessage(ARG_ERROR3);
                exit(1);
            }
            *nargv++ = arg + 2;
        }
    }

    /* Copy the rest of the arguments */
    for (i = 0; i < jargc ; i++) {
        const char *arg = jargv[i];
        if (arg[0] != '-' || arg[1] != 'J') {
            *nargv++ = (arg == NULL) ? NULL : JLI_StringDup(arg);
        }
    }
    for (i = 0; i < argc; i++) {
        char *arg = argv[i];
        if (arg[0] == '-') {
            if (arg[1] == 'J')
                continue;
            if (IsWildCardEnabled() && arg[1] == 'c'
                && (JLI_StrCmp(arg, "-cp") == 0 ||
                    JLI_StrCmp(arg, "-classpath") == 0)
                && i < argc - 1) {
                *nargv++ = arg;
                *nargv++ = (char *) JLI_WildcardExpandClasspath(argv[i+1]);
                i++;
                continue;
            }
        }
        *nargv++ = arg;
    }
    *nargv = 0;
}
3)加载主类:
/*
 * Loads a class and verifies that the main class is present and it is ok to
 * call it for more details refer to the java implementation.
 */
static jclass
LoadMainClass(JNIEnv *env, int mode, char *name)
{
    jmethodID mid;
    jstring str;
    jobject result;
    jlong start, end;
    jclass cls = GetLauncherHelperClass(env);
    NULL_CHECK0(cls);
    if (JLI_IsTraceLauncher()) {
        start = CounterGet();
    }
    NULL_CHECK0(mid = (*env)->GetStaticMethodID(env, cls,
                "checkAndLoadMain",
                "(ZILjava/lang/String;)Ljava/lang/Class;"));

    str = NewPlatformString(env, name);
    CHECK_JNI_RETURN_0(
        result = (*env)->CallStaticObjectMethod(
            env, cls, mid, USE_STDERR, mode, str));

    if (JLI_IsTraceLauncher()) {
        end   = CounterGet();
        printf("%ld micro seconds to load main class\n",
               (long)(jint)Counter2Micros(end-start));
        printf("----%s----\n", JLDEBUG_ENV_ENTRY);
    }

    return (jclass)result;
}

​ 如果是执行class方法。main函数直接调用Java.c中LoadMainClass方法装载该类。然后main函数调用JNIEnv实例的GetStaticMethodID方法查找装载的class主类中“public static void main(String[] args)”方法,并判断该方法是否为public方法,然后调用JNIEnv实例的CallStaticVoidMethod方法调用该Java类的main方法。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值