刚学java时,我们还都不会用ide,那会我们都直接使用javac编译,java来运行;但都是针对单个文件;这个是容易理解的;可当一个完整项目的springboot 的jar包通过java -jar 后是如何运行的呢?
首先我们要知道java是不支持jar包中内嵌jar包的读取的;所以像springboot打包好的jar包,里面是有lib文件夹,里面全是*.jar。这种情况单靠java是不可能读取里面的class文件的。
对于jar文件,我们首先要明白它实际上是哟有两种情况的:
虽然都是以.jar结尾,但功能不同:一个是可运行的jar,目的就是为了可以单独运行;一个是作为依赖的jar(它是不能单独运行的,即使它有内置tomcat也不行的),目的就是被别人引用;
下图是一个可运行jar的内部目录结构和一个依赖jar的内部结构
![]() |
![]() |
1.META-INF目录下都有maven目录,这个目录里面放着pom.xml的配置文件;
1.BOOT-INF是可运行jar包才有的,里面classes目录是咱们写的代码的classs文件,另一个lib就是其他依赖jar包的位置;
2.普通的依赖jar包,则是直接将业务放到与META-INF同级的目录中。
3.可运行jar的META-INF下无services目录,普通jar有
4.都有的org目录,但里面东西却不同。
我们知道tomcat在没有项目的情况下也是能独立启动的,java一样,即使没有jar包运行,单独也是可运行的,就是我们的jvm。jvm的启动加载运行jvm类加载机制与过程,这里就不再赘述了。
还记得上面的MANIFEST.MF文件吗
这个就是springboot的入口,而这个文件的位置就是根目录org下,这个目录是在咱项目打jar包的时候就生成了的。spring-boot-loader是SpringBoot的引导程序,当你使用Maven-Install打包一个SpringBoot单一JAR包时,SpringBootLoader的代码被拷贝到JAR中。(即这个org目录)
SpringBootLoader读取内嵌JAR资源的关键(内嵌JAR以STORED模式存储)(即:非压缩,另一个模式是:DEFLATED)
我们看到,SpringBootLoader:
在oracle官网找到了该命令的描述:
If the -jar option is specified, its argument is the name of the JAR
file containing class and resource files for the application. The
startup class must be indicated by the Main-Class manifest header in
its source code.
再次秀出我蹩脚的英文翻译:
小结一下:
java -jar会去找jar中的MANIFEST.MF文件的Main-Class,在那里面找到真正的启动类;
所以,从jvm到springboot,实际流程如下。
操作系统底层c/c++启动jre --> 找到MANIFEST.MF文件通过JarLauncher --> Application.java的main方法
上面主要讲了springboot的主类是如何被java找到的,但jvm是如何找到JarLauncher的呢?
下面是我找的一个解释:
1.首先main方法执行需要一个操作来启动,像java Mm这种命令
2.这种命令首先是操作系统解析找到java命令属于jdk的东西,并调用jdk的的启动函数, 就像windows的双击操作一样,双击肯定是操作系统搞了什么小动作打开了软件
3.当操作系统调用了虚拟机的命令后,虚拟机会拿到命令的参数比如 Mm,然后去找编译后的文件
4.虚拟机找到文件后会调用jdk中的java代码,找到这个类sun.launcher.LauncherHelper,这个类作为一个工具类,作为桥梁链接了c++和java代码
5.调用sun.launcher.LauncherHelper类的checkAndLoadMain方法,通过这个方法找执行类Mm的Main方法
6.加载好之后执行Main
这是另一个解释:
在JavaMain()函数(定义在openjdk/jdk/src/share/bin/java.c文件中)中调用LoadMainClass()函数加载Java主类。
我们先看下LoadMainClass的细节,看看她咋加载的。通过GetLauncherHelperClass方法,我们终于看到了熟悉的身影:sun/launcher/LauncherHelper
LoadMainClass做了三件事:
1、获取LauncherHelper实例
2、通过checkAndLoadMain方法使用ClassLoader.getSystemClassLoader初始化org.springframework.boot.loader.JarLauncher class
3、makePlatformString获取utf-8后的string
下面附上这个类的截图
Java 离 Linux 内核到底有多远?
第 1 步,调用 InitializeJVM 初始化 JVM。InitializeJVM 会调用 ifn->CreateJavaVM,也就是libjvm.so 中的 JNI_CreateJavaVM。
第 2 步,LoadMainClass,最终调用的是 JVM_FindClassFromBootLoader,也是通过动态链接找到函数(定义在 hotspot/share/prims/ 下),然后调用它。
第 3 和第 4 步,Java 的同学应该知道,这就是调用 main 函数。
加载主类LoadMainClass
LoadMainClass执行过程,主要是利用JNI方法来获取主类名称,进行类名处理,以及加载主类。
LauncherHelper你的祖坟终于找到了!!!