首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

启动一个没有 main 函数的 java 程序

我正在参加掘金 2020 年度人气创作者评选,欢迎大家帮忙投票:

作为一名 开发者,不知道大家有没有去想过, ?程序为什么一定要从 main 函数执行开始,其实关于这个话题,我大概从网上搜了下,其实不乏有main 方法是我们学习Java语言学习的第一个方法,也是每个 java 使用者最熟悉的方法, 每个 Java 应用程序都必须有且仅有一个 main 方法这种说法。那么真的是这样吗?今天就来聊聊这个事情。

为什么 main 函数是 java 执行入口

我们在通过一般的 IDE 去 debug 时,main 函数确实是在堆栈的最开始地方…

但是如果你熟悉 SpringBoot 的启动过程,你会知道,你看到的 main 函数并不是真正开始执行启动的 main 函数,关于这点,我在之前 SpringBoot 系列-FatJar 启动原理 这篇文章中有过说明;即使是通过 JarLaunch 启动,但是入口还是 main,只不过套了一层,然后反射去调用你应用的 main 方法。

这里抛开 JarLaunch 这种引导启动的方式,单从最普通 java 程序来看,我们来看下 main 函数作为入口的原因。

找的最开始、最遥远的地方

JDK 里面的代码太多了,如果在不清楚的情况下去找,那和大海捞针差不多;那我们想一下,既然 java 要去执行 main,首先它要找到这个 main,那 main 方法是写在我们代码里面的,所以对于 java 来说,它就不得不去先把我们包含 main 方法的类加载起来。所以:

我们找到了 这个上一层入口;通过这个方法的代码注释,我们就知道了,网上关于介绍 main 作为启动方法的一系列验证是缘起何处了:

可以从 fatjar manifest 中找到启动类的 classname

使用 System ClassLoader 加载这个类

验证这个启动类的合法性

这个类是否存在

有没有 main 函数

是不是 static 的

是不是 public 的

有没有 string 数组作为参数

如果没有 main 函数,那当前的这个类是不是继承了 FX Application(关键

PS: 这里摘取一篇关于为什么是 public 的描述:JAVA 指定了一些可访问的修饰符如:private,protected,public。每个修饰符都有它对应的权限,public 权限最大,为了说明问题,我们假设 main 方法是用 private 修饰的,那么 main 方法除了 Demo 这个类对外是不可见的。那么,JVM 就访问不到 main 方法了。因此,为了保证JVM在任何情况下都可以访问到main方法,就用 public修饰

这个说法我个人理解是有点欠妥的,首先是 java 里面有反射机制,访问修饰符的存在在 JVM 规范里面说的最多的是因为安全问题,并不是 JVM 能不能访问的问题,因为 JVM 里面有一百种方式去访问一个 private。

LauncherHelper 被执行调用的地方

从堆栈看,checkAndLoadMain 上层没有了,那猜测可能就是有底层 JVM(c 部分)来驱动的。继续去扒一下,在 jdk 的 java.c 文件中捞到了如下代码片段:

到这也论证了前面的猜测,确实是由底层来驱动执行的。那么既然都看到这里了,也有必要看下我们的 JAVA 程序启动、JVM 启动过程是怎样的。

JVM 是如何驱动 JAVA 程序执行的

这里我的思路还是从可以见的代码及堆栈一层一层往上去拨的,通过 GetLauncherHelperClass 找到了 LoadMainClass,后面再找打整体启动入口。

LoadMainClass

下面是代码(代码的可读性和理解要比文字更直接):

Java 程序的 Entry point

对于 C/C++ 来说,其启动入口和 java 一样,也都是 main。下面我们略过一些无关代码,将 JAVA 程序驱动启动的核心流程代码梳理下

1、入口,main.c 的 main 方法 -> JLI_Launch

2、JLI_Launch,JVM 的实际 Entry point

JVMInit

JVMInit 对于不同的操作系统有不同的实现,这里以 linux 的实现为例:

这里比较深,ContinueInNewThread 里面又使用了一个 ContinueInNewThread0,从代码解释来看,大概意思是:先把当前线程阻塞,然后使用一个新的线程去执行,如果新线程创建失败就在原来的线程继续支持这个函数。核心代码:

JavaMain

1、这里第一个比较关键的就是 InitializeJVM,初始化创建一个 Java Virtual Machine(jvm.so -> CreateJavaVM 代码比较多,实际上真正的初始化和启动jvm,是由 jvm.so 中的JNI_CreateJavaVM 实现)。

2、接下来就是到我们前面反推到的 LoadMainClass 了,找到我们真正 java 程序的入口类,就是我们应用程序带有 main 函数的类。

3、获取应用程序 Class -> GetApplicationClass,这里简单说下,因为和最后的那个 demo 有关,也和本文的题目有关。

4、调用 main 函数执行应用进程启动

简单回顾

对于平常我们常见的 java 应用程序来说,main 函数确实作为执行入口,这个是由底层 JVM 驱动执行逻辑决定。但是从整个分析过程也可以看出,main 函数并不是唯一一种入口,那就是以非 main 入口启动的方式,也就是 JavaFX。

使用 FX Application 方式启动 java 程序

JAVA GUI 的旅程开始于 AWT,后来被一个更好的 GUI 框架所取代,其被称为 Swing。Swing 在GUI 领域有将近 20 年的历史。但是,它缺乏许多当今需求的视觉功能,其不仅要求可在多个设备上运行,还要有很好的外观和感觉。在 JAVA GUI 领域最有前景的是JavaFX,JAVA 自带的三个 GUI 工具包--AWT,Swing,和 JavaFX ?-- 它们做几乎相同的工作。而 JavaFX 使用一些不同的方法进行 GUI 编程,本文不针对 JavaFX 展开细说,有兴趣的同学可以自行查阅。

每一个 JavaFX 应用程序是应用程序类的扩展,其提供了应用程序执行的入口点。一个独立的应用程序通常是通过调用这个类定义的静态方法来运行的。应用程序类定义了三个生命周期的方法:init(), start() 和 stop()。

那么结合上一节中关于启动入口的讨论,这里给出一个小 demo 来把一个 springboot 工程启动起来(基于 ide,java -jar 可能会有区别,这里未验证)

这里有一个有意思的情况,一般情况下,如果没有非守护线程存活(通常是 web 模块提供)时进程会在启动完之后就退出,但是这里我没有开启 web 端口,但是启动完时,进程并没有退出,即使在 start 里面抛出异常,也不能显示的去阻断,这和 JavaFX Application 的生命周期有关,前面有提到。

总结

JAVA 应用的启动不一定是非要是 main 作为入口,关于其他的引导启动方式没有继续调研,如果大家有知道其他方式,也欢迎留言补充。

参考

理解基本的JavaFX类,并知道如何使用它们

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20210124A014XP00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券
http://www.vxiaotou.com