从JDK源码级深入剖析main方法的运行机制
阅读原文时间:2023年08月16日阅读:1

如果你是一名Java应用开发工程师,你应该对“public static void main(String[] args)”这段代码再熟悉不过了,然而你是否了解main方法是如何调用的,为什么我们运行java.exe,就能启动应用程序?下面,让我们来一探究竟吧!

如果你下载了OpenJDK源码,在源码目录src\java.base\share\native\libjli目录下有java.c这样一个文件,java.exe文件是通过编译java.c文件生成的可执行文件。java.c文件是Java Runtime Environment(JRE)的一部分,它是用C语言编写的,用于启动Java应用程序并运行Java字节码。

在java.c文件当中,有这样一个方法:

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

这个方法是Java虚拟机的入口点,负责解析命令行参数、加载Java应用程序并启动Java虚拟机。在JLI_Launch方法中,会解析命令行参数,包括指定Java类路径、指定JVM参数等,然后调用JVM的启动函数,启动Java虚拟机并加载Java应用程序。也就是说,当你运行“java.exe YourClassName”这个命令时,调用的就是JLI_Launch这个方法。

我们进入方法内部,我们看到方法的最后一行代码:

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

该代码作用是对Java虚拟机进行初始化,如果你现在是windows系统中运行java应用程序,那么该方法的实现是在src\java.base\windows\native\libjli\java_md.c这个文件中:

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这个方法,发现了

rslt = CallJavaMainInNewThread(threadStackSize, (void*)&args);

这段代码,继续进入CallJavaMainInNewThread,找到了这段代码

rslt = JavaMain(args);

因此我们知道了:Java类中的main方法public static void main(String[] args),就是由java.c文件中JavaMain方法调用的。

上代码:

int JavaMain(void* _args)
{
    JavaMainArgs *args = (JavaMainArgs *)_args;
    ...
    //初始化Java虚拟机
    if (!InitializeJVM(&vm, &env, &ifn)) {
        JLI_ReportErrorMessage(JVM_ERROR1);
        exit(1);
    }
    ...
    //加载主运行类
    mainClass = LoadMainClass(env, mode, what);
    CHECK_EXCEPTION_NULL_LEAVE(mainClass);
    ...
    //通过加载的主运行类,获取main方法
    mainID = (*env)->GetStaticMethodID(env, mainClass,"main","([Ljava/lang/String;)V");
    CHECK_EXCEPTION_NULL_LEAVE(mainID);
    //调用main函数
    (*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);
    ...
}

这里可能就有读者要问了,C程序是怎么调用Java程序的?
实际上,通过JNI(java本地接口,是Java Native Interface的缩写)技术可以实现C语言调用Java,JNI是Java平台提供的一种编程框架,用于实现Java应用程序与本地(非Java)应用程序之间的相互调用。JNI提供了一组规范和工具,使Java应用程序能够调用C、C++和其他本地语言编写的代码,反之亦然。通过JNI,Java程序员可以利用现有的本地代码库,并与其他本地应用程序进行交互,从而扩展Java应用程序的功能和性能。