Java的main方法是如何被调用的

2023-03-15 13:43:42 浏览数 (2)

本文将从源码角度看下Java的main方法是如何被调用的。OpenJDK版本

➜ jdk hg id 76072a077ee1 jdk-11 28

当我们运行Java命令后,Java程序本身的main方法会首先被执行

C文件src/java.base/share/native/launcher/main.c

代码语言:javascript复制
JNIEXPORT int
main(int argc, char **argv)
{
    int margc;
    char** margv;
    int jargc;
    char** jargv;
    const jboolean const_javaw = JNI_FALSE;
    ...
    return JLI_Launch(margc, margv,
                   jargc, (const char**) jargv,
                   0, NULL,
                   VERSION_STRING,
                   DOT_VERSION,
                   (const_progname != NULL) ? const_progname : *margv,
                   (const_launcher != NULL) ? const_launcher : *margv,
                   jargc > 0,
                   const_cpwildcard, const_javaw, 0);
}

之后进入JLI_Launch方法

C文件src/java.base/share/native/libjli/java.c

代码语言:javascript复制
/*
 * Entry point.
 */
JNIEXPORT int JNICALL
JLI_Launch(int argc, char ** argv,              /* main argc, argv */
        int jargc, const char** jargv,          /* java args */
        int appclassc, const char** appclassv,  /* app classpath */
        const char* fullversion,                /* full version defined */
        const char* dotversion,                 /* UNUSED 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                               /* unused */
)
{
    ...
    InvocationFunctions ifn;
    ...
    char jvmpath[MAXPATHLEN];
    char jrepath[MAXPATHLEN];
    char jvmcfg[MAXPATHLEN];
    ...


    // 找到jvmpath,例如 /usr/lib/jvm/jdk-11/lib/server/libjvm.so
    CreateExecutionEnvironment(&argc, &argv,
                               jrepath, sizeof(jrepath),
                               jvmpath, sizeof(jvmpath),
                               jvmcfg,  sizeof(jvmcfg));


    ...
    // 加载libjvm.so,并获取其 JNI_CreateJavaVM 方法
    if (!LoadJavaVM(jvmpath, &ifn)) {
        return(6);
    }
    ...
    return JVMInit(&ifn, threadStackSize, argc, argv, mode, what, ret);
}

之后进入JVMInit方法

C文件src/java.base/unix/native/libjli/java_md_solinux.c

代码语言:javascript复制
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);
}

再进入ContinueInNewThread方法

C文件src/java.base/share/native/libjli/java.c

代码语言:javascript复制
int
ContinueInNewThread(InvocationFunctions* ifn, jlong threadStackSize,
                    int argc, char **argv,
                    int mode, char *what, int ret)
{
    ...
    { /* Create a new thread to create JVM and invoke main method */
      JavaMainArgs args;
      int rslt;


      args.argc = argc;
      args.argv = argv;
      args.mode = mode;
      args.what = what;
      args.ifn = *ifn;


      rslt = ContinueInNewThread0(JavaMain, threadStackSize, (void*)&args);
      /* If the caller has deemed there is an error we
       * simply return that, otherwise we return the value of
       * the callee
       */
      return (ret != 0) ? ret : rslt;
    }
}

该方法最终会调用ContinueInNewThread0方法,开启一个系统线程,且该线程的入口函数是JavaMain。看下JavaMain方法

C文件src/java.base/share/native/libjli/java.c

代码语言:javascript复制
int JNICALL
JavaMain(void * _args)
{
    ...
    // 该方法会调用libjvm.so里的JNI_CreateJavaVM方法对JVM进行初始化
    if (!InitializeJVM(&vm, &env, &ifn)) {
        JLI_ReportErrorMessage(JVM_ERROR1);
        exit(1);
    }
    ...
    // 如果我们运行Java时传入了--version参数,下面就会输出version并结束Java进程
    if (printVersion || showVersion) {
        PrintJavaVersion(env, showVersion);
        CHECK_EXCEPTION_LEAVE(0);
        if (printVersion) {
            LEAVE();
        }
    }


    ...
    mainClass = LoadMainClass(env, mode, what);
    ...
    mainArgs = CreateApplicationArgs(env, argv, argc);
    ...
    mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
                                       "([Ljava/lang/String;)V");
    ...
    /* Invoke main method. */
    (*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);


    ...
    ret = (*env)->ExceptionOccurred(env) == NULL ? 0 : 1;
    LEAVE();
}

该方法就是我们的最终方法,它会先调用InitializeJVM初始化JVM,再通过一系列的方法获取mainClass、mainArgs,最终调用(*env)->CallStaticVoidMethod 方法真正的执行我们提供的Java main方法。

至此,整个流程也就结束了。

有关(*env)->CallStaticVoidMethod究竟是如何执行的Java main方法,以及Java main方法又是如何调用的其他Java方法,我们之后会另起文章详细分析。

0 人点赞