执行Java -jar somefile.jar时发生了什么(一)


最近在阅读JVM源码,把一些心得写成Blog分享出来,于是便新开了这么一个专题。


第一篇文章取名字的时候让我非常困惑,我代码的阅读是从Launcher开始入手的,也就是Java.exe(如果是windows平台的话)对应的相关代码,但我又不能取“JVM启动过程分析”之类的名字,因为从分析主流程的角度来讲还深不到这个层次,所以就暂且起了这么一个奇怪的名字。


这个系列如果能继续下去的话,不加特殊说明,使用的JDK和JVM版本均为8u20,下载地址来自OpenJDK:http://hg.openjdk.java.net/jdk8u


本人是一个JAVA程序员,在分析JVM大量C/C++时难免会有不妥当的地方,还希望各位读者指正。介于时间有涯而代码“无涯”,具体的实现细节不可能面面俱到,只着重分析和看懂大致流程和机制,如果有比较重要的细节遗漏之处,欢迎留言讨论。


在这个系列中,任何对源文件位置的描述都使用相对路径,其中jdk/代表放置Jdk源码的根目录,hotspot/代表放置jvm源码的根目录。


扫描二维码关注公众号,回复: 5548625 查看本文章

一、Launcher代码分析

(1)Main.c中的main

位置:jdk/src/bin/main.c 

当我们调用java命令时,首先肯定进入的是C/C++的main函数,就像若干年前我写的那个helloworld一样。

这个main函数位于jdk/src/bin/main.c,在JDK8中是放置在这个位置,在以前较老版本是放在JVM相关代码中的。

main.c中几乎没有实质的逻辑,主要是复制一些参数,处理在windows平台中的一些调用,之后就把各参数传递给JLI_launch进行执行。相关代码在第125行。

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



(2)java.c中的JLI_Launch

位置:jdk/src/bin/java.c

java.c中根据注释可以大概看出这个函数的各参数含义:

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 */

在第236行调用系统函数获取环境变量,给jrepath,jvmpath和jvmcfg赋值。(每个Java基础教程里设置的环境变量在这里起作用)

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

第248行加载jvm.dll这个文件,只是加载文件到内存,并没有执行任何操作,同时给这个ifn结构体赋值(根据dll抽取函数调用地址赋值),ifn结构体包含三个关键的函数指针。

if (!LoadJavaVM(jvmpath, &ifn)) {
        return(6);
    }
Ifn结构如下:

typedef struct {
    CreateJavaVM_t CreateJavaVM;
    GetDefaultJavaVMInitArgs_t GetDefaultJavaVMInitArgs;
    GetCreatedJavaVMs_t GetCreatedJavaVMs;
} InvocationFunctions;
根据函数名可以大概猜到,第一个是创建虚拟机,第二个是获取初始参数,第三个是创建很多虚拟机?(vms代表虚拟机的复数形式?我猜的。。)。

之后又对参数进行了一些额外的处理(包括获取classpath、打印调试信息、添加额外的参数等等),在299行调用JVMInit初始化虚拟机

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


(3)Java.c中的JVMInit

位置:jdk/src/bin/java.c

代码很少,首先打印一下信息,然后调用ConitueInNewThread函数,从函数名也可以看出,会启用一个新线程建立JVM。

int
JVMInit(InvocationFunctions* ifn, jlong threadStackSize,
        int argc, char **argv,
        int mode, char *what, int ret)
{
    ShowSplashScreen();
    return ContinueInNewThread(ifn, threadStackSize, argc, argv, mode, what, ret);
}


(4)Java.c中的ContinueInNewThread

位置:jdk/src/bin/java.c

这个函数依然没做啥东西,就是封装了一下参数,然后委派给ContinueInNewThread0。而ContinueInNewThread0开启一个新的线程,执行JavaMain函数。


(5)Java.c中的JavaMain

位置:jdk/src/bin/java.c

首先在第371行初始化JVM,而InitializeJVM函数做的工作就是调用ifn->createJavaVM,具体的JVM启动过程水太深因此不在这里进行分析。如果初始化成功,则会给vm对象和env对象赋值,其中env是一个非常重要的对象,进行JNI调用的时候会频繁用到。

if (!InitializeJVM(&vm, &env, &ifn)) {
        JLI_ReportErrorMessage(JVM_ERROR1);
        exit(1);
    }
之后检查一下是否传入了Jar文件或者一个类名,如果没有的话就打印一下Usage

之后第439行获取主类,获取主类的方式挺有意思的,在后面会进行分析。接着看看是否抛异常,如果抛异常就直接退出了。

    mainClass = LoadMainClass(env, mode, what);
    CHECK_EXCEPTION_NULL_LEAVE(mainClass);


随后针对JavaFX加载一下东西(如果需要的话),然后获取main这个方法的ID,组合参数,并Invoke,水到渠成。

其中每一步都检查是否有异常抛出。

 mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
                                       "([Ljava/lang/String;)V");
    CHECK_EXCEPTION_NULL_LEAVE(mainID);

    /* Build platform specific argument array */
    mainArgs = CreateApplicationArgs(env, argv, argc);
    CHECK_EXCEPTION_NULL_LEAVE(mainArgs);

    /* Invoke main method. */
    (*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);

    /*
     * The launcher's exit code (in the absence of calls to
     * System.exit) will be non-zero if main threw an exception.
     */
    ret = (*env)->ExceptionOccurred(env) == NULL ? 0 : 1;



(未完待续)






猜你喜欢

转载自blog.csdn.net/ROger__wonG/article/details/39900717