java -jar xx.jar是如何运行的


前言

刚学java时,我们还都不会用ide,那会我们都直接使用javac编译,java来运行;但都是针对单个文件;这个是容易理解的;可当一个完整项目的springboot 的jar包通过java -jar 后是如何运行的呢?


一、jar包是什么?

首先我们要知道java是不支持jar包中内嵌jar包的读取的;所以像springboot打包好的jar包,里面是有lib文件夹,里面全是*.jar。这种情况单靠java是不可能读取里面的class文件的。
对于jar文件,我们首先要明白它实际上是哟有两种情况的:

  1. 一种就是咱们经常能看到的,通过springboot打包好的可运行jar,这是一种;
  2. 还有一种就是我们缺少思考没有想过,但却能看到的,就是我们开发引用的jar包,也就是springboot打包好的jar包中的lib文件夹下的jar包,这些jar包,或者说依赖,也都是一个个单独的jar包;

二、他们的区别

1.功能目的

虽然都是以.jar结尾,但功能不同:一个是可运行的jar,目的就是为了可以单独运行;一个是作为依赖的jar(它是不能单独运行的,即使它有内置tomcat也不行的),目的就是被别人引用;

2.文件目录

下图是一个可运行jar的内部目录结构和一个依赖jar的内部结构

图1 这是一个简单Springboot项目的目录结构
图2 这是logback-classic-1.2.10.jar的目录结构
这个只是给出了目录结构,具体不同,大家可以自己找两个jar包仔细对比的看下。这里只说结论:

相同点:

1.META-INF目录下都有maven目录,这个目录里面放着pom.xml的配置文件;
在这里插入图片描述

2.META-INF目录下都有MANIFEST.MF。
在这里插入图片描述

不同点:

1.BOOT-INF是可运行jar包才有的,里面classes目录是咱们写的代码的classs文件,另一个lib就是其他依赖jar包的位置;
2.普通的依赖jar包,则是直接将业务放到与META-INF同级的目录中。
3.可运行jar的META-INF下无services目录,普通jar有
在这里插入图片描述
4.都有的org目录,但里面东西却不同。
在这里插入图片描述

3.运行原理

我们知道tomcat在没有项目的情况下也是能独立启动的,java一样,即使没有jar包运行,单独也是可运行的,就是我们的jvm。jvm的启动加载运行jvm类加载机制与过程,这里就不再赘述了。

1.springboot的入口

还记得上面的MANIFEST.MF文件吗
在这里插入图片描述
这个就是springboot的入口,而这个文件的位置就是根目录org下,这个目录是在咱项目打jar包的时候就生成了的。spring-boot-loader是SpringBoot的引导程序,当你使用Maven-Install打包一个SpringBoot单一JAR包时,SpringBootLoader的代码被拷贝到JAR中。(即这个org目录)

SpringBootLoader读取内嵌JAR资源的关键(内嵌JAR以STORED模式存储)(即:非压缩,另一个模式是:DEFLATED)

2.Springbootloader作用

我们看到,SpringBootLoader:

  1. 接管了SpringBoot程序的启动入口(Main-Class)
  2. 对JDK中java.util.zip.ZipFile实现了扩展,以便支持内嵌JAR中class的随机访问
    3.提供了SpringBoot-ClassLoader负责BOOT-INF下classes、lib的资源加载(Spring-Boot-Classes、Spring-Boot-Lib内容构成了应用真正的class-path)
    通俗来讲就是,我们开发认为的主方法,并不是程序运行真正的主方法,它的前面还有JarLauncher;
    4.是JarLauncher最终通过JarFile类的成员变量manifestSupplier关联上的:在这里插入图片描述
    5.jvm如何识别MANIFEST.MF文件的Main-Class?

在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.

再次秀出我蹩脚的英文翻译:

  1. 使用-jar参数时,后面的参数是的jar文件名(本例中是springbootstarterdemo-0.0.1-SNAPSHOT.jar);
  2. 该jar文件中包含的是class和资源文件;
  3. 在manifest文件中有Main-Class的定义;
  4. Main-Class的源码中指定了整个应用的启动类;(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

下面附上这个类的截图

在这里插入图片描述
在jvm核心包rt.jar中。

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你的祖坟终于找到了!!!

Java → JVM → glibc → 内核
JVM启动过程源码流程分析

  • 0
    点赞
  • 5
    收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
评论

打赏作者

alan2hl

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值