当前位置: 首页 > 科技观察

没想到在进入Main函数之前发生了这么多事情!

时间:2023-03-21 01:12:48 科技观察

题外话最近轩辕有些迷茫。工作和生活中忙于很多事情,几乎抽不出时间静下心来写文章。点灯烧油,一直写到1点才写完。估计小伙伴们也发现了,这段时间的故事和文章少了很多,确实是这样,不像水文学,点几下就搞定了,不像一般的技术文章跟着标准流程。写故事需要放飞自我,有了好的灵感才能一口气完成。这段时间选了好几次选题,都是抢先占了坑,又因为没时间思考,所以一直留着。。。所以今天,写了另一个顶一个顶,故事文章再讨论。给我一点时间,谢谢您的支持!之前的一篇文章讲过从创建进程到进入main函数是怎么回事?但当时只是针对C/C++等原生语言,从操作系统(Linux&Windows)层面讨论了程序的启动过程,并未提及基于虚拟机的语言/Java和Python等解释器。今天我们就来探讨下你写的main方法在Java语言中是如何执行的?对于Java来说,底层是运行的Java虚拟机,即JVM。本文如无特别说明,Hotspot默认为研究对象。我们先回顾一下那篇文章。对于C/C++程序,从创建进程到进入main函数,主要有四个阶段:Process&mainthreadcreationstage主线程开始执行,进行进程级的初始化操作(如Loadthesystemdynamiclinklibrary)主线程进入可执行文件的入口点(OEP),初始化C/C++运行时库。从C/C++运行时库调用主函数。大家知道,Java虚拟机JVM主要是用C++写的,所以JVM本质上是一个C++程序。所以,以上四个阶段同样适用于JVM。只不过,对于C/C++程序来说,main函数已经进入到这里了,话题就可以结束了。对于Java程序来说,执行到JVM的main,一切才刚刚开始。JVM的正篇从JVM的main函数说起……你应该知道不管你是普通的Java程序,还是使用Spring或者其他任何框架,最终的程序运行在一个Java进程中,可执行文件此进程的文件是exe(在Windows上)或elf(在Linux上)。让我们从这个可执行文件开始。以Linux系统上的Java8版本为例,可以看到这个可执行文件的入口和我们上一篇分析的流程是一致的。进入这个程序启动入口后,会经过一系列的调用,最后来到main函数:反汇编看起来像个大头。幸运的是,HotSpot虚拟机有一个开源版本。我们可以去OpenJDK中找到main函数的源码。不同版本之间差异还是挺大的,这里以Java8为例:代码路径:https://github.com/openjdk/jdk/blob/jdk8-b20/jdk/src/share/bin/main.c在这个代码中,除了Main函数,还可以看到如果定义了JAVAW宏定义,入口由main变为WinMain函数。做过Windows应用开发的朋友这时候应该会满意地笑了。如果定义了JAVAW,它就是一个Win32GUI程序。当然,Linux上并没有这样的宏定义,但这不是本文的主题。可以看出main函数只是一个包装器,直接进入JLI_Launch。这个函数位于同一目录隔壁的java.c文件中。它是JVM的一个非常重要的初始化函数。它主要完成以下事情:参数解析、环境配置检查、Java运行环境加载、JVM核心动态库libjvm.so的创建和初始化Java虚拟机对象的这些过程不是我们本文研究的目标。让我们继续关注Java中的main函数是如何调用的。在JLI_Launch的最后调用了ContinueInNewThread,从这个函数的名字我们也可以窥探到它的作用。这个函数还是做了一层封装,内部调用了真正工作的函数ContinueInNewThread0:接下来就是创建一个线程来继续下面的事情,但是创建线程涉及到调用操作系统API,所以这个函数在不同版本的系统都有相应的实现。看传给它的第一个参数,就是新线程启动后要执行的入口函数:JavaMain。函数JavaMain的名称有点有趣。看来是要进入Java的境界了。继续阅读:intJNICALLJavaMain(void*_args){//...//寻找启动类mainClass=LoadMainClass(env,mode,what);//...//在启动类mainID中寻找main函数=(*env)->GetStaticMethodID(env,mainClass,"main","([Ljava/lang/String;)V");//...//Callit(*env)->CallStaticVoidMethod(env,mainClass,mainID,mainArgs);//...}JavaMain里面的细节还是挺多的,我们把需要用到的抽出来关心,我们写main方法就像把大象锁进冰箱。分三步:找到启动类在启动类中找到main方法并调用最后,它们都是以class文件的形式存放的,所以这个查找的背后少不了类加载等一系列工作。总之,操作猛如虎。咦,JVM找到我们写的main方法了!下一步是调用它。进入Java世界调用main方法就是CallStaticVoidMethod。正如您从名称中看到的那样,这是调用一个具有null返回值的静态方法。注意,C++的疆域即将到达边界,我们即将通过它来到Java美丽的新世界!这个函数稍后会出现:JavaCalls::call(result,method,&java_args,CHECK);最后Java会创建Method栈帧,准备模板解释器,然后转到解释器入口开始执行字节码,正式进入Java世界!进入Java世界的第一站就是前面找到的启动类的main方法,Java世界之旅从这里开始程序。总结现在我们可以回答这个问题了:从创建进程到Java的main方法发生了什么?我们把它分为三个大阶段:第一阶段:在操作系统层面创建进程和主线程第二阶段:主线程开始执行,进入Java可执行文件中的main函数(C++级别)(exe/elf)第三阶段:创建JVM,在启动类中找到main方法,启动解释器执行相应的字节码,进入Java世界来自微信公众号《编程技术宇宙》,作者轩辕志峰。转载本文请联系编程技术宇宙公众号。