jvm虚拟机是如何进入spring-boot-loader中JarLauncher.main方法的

前提

关于springboot的类加载原理和spring-boot-loader的源码解析,网上已有很多的很棒的文章了。我一直相信对于技术原理的获取,代码层面的话一定是动眼比不上动手,debug跟着源码运行走一遍,效果往往是很棒的。同理,对于spring-boot-loader的原理,源码的掌握,其原理和debug方式参考:springboot jar包可运行,debug可视化告诉你怎么运行的

本文想要讲述的可能要更近一步了。带着一些疑问开始我们的学习

  • 1、程序是从哪进入JarLauncher.main()方法的
  • 2、java -jar xxx-executable.jar java有什么规定吗
  • 3、jvm层面是如何进入到java程序的,连接点在哪里

前期储备:

  1. 你需要知道我们平时写的使用springboot架构的应用程序,运行时的起始点不是我们自定义的app.java的main方法,而是springboot的JarLauncher.java的main方法。如果你需要了解这个,请参考:springboot jar包可运行,debug可视化告诉你怎么运行的

在可视化这个过程中,在java侧我们要debug org.springframework.boot.loader.JarLauncher,所以我们需要使用它的源码,所以需要引用JarLauncher的源码jar包,如下

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-loader</artifactId>
<version>2.4.4</version>
</dependency>

》本文力求专注和精简,希望你有所收获和想法

正文

-jar的规矩

其实,java-jar定义了一些规则,只要符合这个规则,你做啥都行,怎么做都行。其中的一个是:jar中需要一个META-INF/MAINFEST.MF文件,且文件中Main-Class manifest header。官网的说明如下

20210417141240

所以,我们解压一个xxx-executable.jar,会看到META-INF/MAINFEST.MF的文件及其内容
20210417141952

正是因为有了Main-Class: org.springframework.boot.loader.JarLauncher,所以,当java -jar xxx-executable.jar时,程序会进入org.springframework.boot.loader.JarLauncher执行main方法。到这里我又有了疑问,那是从哪进入JarLauncher.main方法的呢

jvm进入java

程序是怎么进入JarLauncher.main方法的呢

这个时候想到了jvm。应该是从jvm进去到了JarLauncher.main,那么怎么验证呢。

我们通过CLion查看jvm源码,试图找到这个答案。我们通过全局搜索Main-Class,Manifest 这些关键词来定位所在的文件,并打上相应的debug端点。如下图

然后我们启动debug模式启动jvm,启动时配置上我们的应用jar包:-jar xxx-executable.jar,如下图
20210417144842

现在我们跟着jvmdebug一起追下寻找Main-Class,Manifest 的过程。
20210417162905

20210417162932

看到了JLI_ParseManifest,从名字也看的出来这个眉目了
20210417162933

可以看到static const char *manifest_name = "META-INF/MANIFEST.MF"在jvm中是个常量。所以,我们的应用xxx-executable.jar中必须有个叫这个名的目录和文件。方法接着解析了这个文件,文件的内容和我们解压xxx-executable.jar看到的内容一致,如下图

所以,到这Main-Class的值就被解析出来了。下面我们看下jvm如何加载Main-Class的值(即org.springframework.boot.loader.JarLauncher)的。

下面图中6个部分其实将虚拟机的整个初始化和加载过程都显示出来了。在这里我们只关注JarLauncher是如何加载初始化与JarLauncher.main是怎么运行的,所以我们重点关注(1)、(2)、(4)、(6)

(1)LoadMainClass - 加载main class

我们先看下LoadMainClass的细节,看看它咋加载的。下图代码已给出,通过GetLauncherHelperClass方法,我们终于看到了熟悉的身影:sun/launcher/LauncherHelper 这个java class。如下图
20210417181757

LoadMainClass做了三件事:
1、获取LauncherHelper实例
2、通过checkAndLoadMain方法使用ClassLoader.getSystemClassLoader初始化org.springframework.boot.loader.JarLauncher class
3、makePlatformString获取utf-8后的string

需要特别指出的是LauncherHelper.java。这个类的源码在jvm中,在我们熟悉的rt.jar是以.class的形式出现的。我把他俩放在一张图片里对比下

20210417185738

(2)获取LauncherHelper的实例

这里的实例是在(1)中初始化好的,用就好了

(4)验证和加载main方法

这里用了类似反射的方法来获取main方法

(6)调用LauncherHelper.main方法

这里就是我们一直要知道的那个地方,也是文章开头部分的问题1和3的答案。LauncherHelper.main即是 jvmjava 的连接的那座桥。

java进入springboot

以上分析了jvm怎么进入java的,现在说下从java怎么进入springboot的,具体的地方是在LauncherHelper类中,在LauncherHelper的checkAndLoadMain方法中,通过ClassLoader的loadclass方法加载var3,var3即是Main-Class的value,所以,loadclass方法即是加载和实例化org.springframework.boot.loader.JarLauncher;进而调用validateMainClass()方法,如下图
20210619094306

20210619094306

validateMainClass()的关键点在通过getMethod这个反射方法调用JarLauncher的main方法,从而实现进入到springboot的JarLauncher.java的main方法。看下validateMainClass()方法的内容逻辑,如下图
20210619094948

进入JarLauncher.java的main方法
20210619095342

流转图

利用CLionIdea的分别debug,我人为的把他俩的运行结合在一起,具象的表示出这两个点的运行,如下图

20210619111004

it`s time to summary

到这里,我们的疑问都找到答案了。我们从jvm源码到java代码,整个流程串下来。相信有人再问你springboot的入口时,你不仅知道了是JarLauncher.main,而且能进一步知道从哪(怎么)进入的JarLauncher.main的。这不管是对工作,还是面试,对你都是有很大收益的。

原味地址:jvm怎么运行springboot jar文件的

附录

20210417192215

现在,从jvmjava,又从从javaspringboot,我们都知道了;又前一篇讲了 从springboot应用app程序的入口和连接点。所有,现在,从jvm到应用app程序的整个过程我们都知道了,如下

1
java.c的JavaMain方法 --> LauncherHelper.java的main方法 --> JarLauncher.java的main方法 -->  Application.java的main方法

受益文章:why-the-jar-of-springboot-can-run-independently