从JDK源码级深入剖析main方法的运行机制
如果你是一名Java应用开发工程师,你应该对“public static void main(String[] args)”这段代码再熟悉不过了,然而你是否了解main方法是如何调用的,为什么我们运行java.exe,就能启动应用程序?下面,让我们来一探究竟吧!
首先,聊一聊,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.exe后发生了什么
在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这个方法。
接着,看一看,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方法调用的。
接着,瞧一瞧,JavaMain这个方法是如何调用main方法的
上代码:
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应用程序的功能和性能。
最后,我们用流程图总结一下