java执行class找不到main函数_你所不知道的HelloWorld背后的执行原理

专注于Java领域优质技术,欢迎关注

作者:饭谈编程

【今日最佳】对于程序员而言,所谓的二八定律指的是 花百分之八十的时间去学习日常研发中不常见的那百分之二十的原理。

据说阿里某程序员对书法十分感兴趣,退休后决定在这方面有所建树。于是花重金购买了上等的文房四宝。

一日,饭后突生雅兴,一番磨墨拟纸,并点上了上好的檀香,颇有王羲之风范,又具颜真卿气势,定神片刻,泼墨挥毫,郑重地写下一行字:hello world。

6cd635a58befde380ebf2666ef006a28.png

当然了,这是个专属程序员的段子哈哈哈。

那么问题来了,写了这么久的Hello World,大家确定自己了解自己写的东西背后是什么原理吗?(o≖◡≖)

【给出2分钟,该知识点涉及到了Java程序执行流程,包括编译、加载和执行,你是否能够理清呢?】

接下来进入严肃时间 (@ ̄ー ̄@)

与众不同的Hello World

c6e38da02ad74865a77aed9c2f136d3c.png

整个代码的执行过程可以分为三个阶段:

  • 代码编译
  • 类加载
  • 类执行

代码编译

代码编译的作用就是将我们编写的 Main.java文件转化为Main.class文件,.class在这里又被称为字节码文件,打开就是一堆的火星文【反正就是看不懂】,在这里我们可以将编译的过程看作生产JVM原料的过程,使用的工具就是jdk提供的工具javac。 大致流程如下:

  • 词法分析,即将源代码的字符流转变为Token集的过程。白话文描述下,就是在我们实际编程中,单个字符是最小单位,而实际上在编程过程中,标记才是最小单位,如关键字、变量名、字面量、运算符等都可以成为Token,貌似还是有点蒙蔽,举个例子(>﹏<),比如整型int在我们编程中它就是三个字符组成的,就是i、n、t 三个字符,而在编译过程中它就是一个Token,不可拆分。

这个过程对我们来说其实是完全屏蔽的,但是实际上它是现代经典编译原理的 套路,词法分析也是为了给后面编译做准备的】

  • 语法分析,通过词法分析拿到Token集后,下一步就是构建抽象语法树了,所谓的抽象语法树其实就是一种用来描述程序代码语法结构的树形表示方式,其中语法树的每一个节点都代表着程序代码中的一个语法结构,如包、类型、修饰符、运算符等。

在我们眼中,Main.java已经可以清晰理解到底写的是什么东西了,但是对于JVM来说还是一脸懵逼的,所以才需要构建成语法树,在这一步后就不会再对源码文件进行操作了,后续的操作都建立在抽象语法树上

  • 填充符号表,符号表是由一组符号地址和符号信息构成的表格,这个表格在编译的不同阶段都会被用到,如在目标代码生成阶段,会对符号名进行地址分配,而符号表就是地址分配的依据。
  • 语义分析,语义分析阶段也可以说是语义检测阶段,上面说到语法分析会构建一棵语法树,那么这棵语法树是否是正确合理的,就由语义分析来做了,语义分析会通过标注检查和数据及控制流分析检查两步入手,在生成字节码的最后一步信息把关。
  • 字节码生成,这是javac编译过程的最后一个阶段了,字节码生成阶段并不只是简简单单的将前面各个步骤生成的信息转化成字节码然后放入磁盘中,还进行了少量的代码添加和转换工作,如我们自己重载的构造函数。

编译期到这里就结束了,那么由谁来将这些原料传输给JVM虚拟机呢?这个时候就要看看类加载的过程了。

类加载

类加载简单来说就是将由类加载器将编译后的字节码文件【Main.class】加载到虚拟机中 ,那么自然而然的,要先介绍下四种类加载器

说说四种类加载器

3656f0094d20ddc86211c9e5134e219f.png

可以从上图中看出,类加载器可以分为四种,而第四种是由我们自己实现的,那么其他三种由JVM提供的类加载在我们启动该Main程序的过程中起到了什么作用呢?

首先说说启动类加载器 Bootstrap ClassLoader ,启动类加载器的作用主要是加载 %JAVA_HOME%jrelib.jar 类库,将其加载到虚拟机内存中,那么rt.jar类库到底有什么作用呢?rt.jar下包含了Java的基础类库,也就是Java doc里面看到的所有的类的class文件,感兴趣的朋友可以自己打开目录看下。

其次是扩展类加载器 Extension ClassLoader ,扩展类加载器的作用主要是负责加载JAVA_HOMEjrelibext目录下的所有类库,主要是载入扩展包。

再者是系统类加载器 Application ClassLoader, 也称之为应用程序类加载器,负责加载用户类路径(也就是我们配置的CLASSPATH)上所指定的类库,是应用程序中默认的类加载。

看完以上三个类加载器的简单描述过程,是不是有种终于知道了我们配置的jdk环境的最终作用了吧,是的,就是让类加载器识别到后加载各种类库。

那么问题来了?是哪个类加载器加载了我们的Hello World程序呢?是的,就是应用程序中默认的类加载器 Application ClassLoader。

知道了类加载器后,那接下来总要了解下类加载器怎么加载的吧?

说说类加载的过程

422137b3b8aced2d306d3c370d568d30.png

网上找了张图片,简单明了。虽说是简单明了,不过确实异常重要的,因为是面试热点(✿◡‿◡)

加载

其实就是上文说到的系统类加载器 Application ClassLoader将编译后的Main.class文件加载到内存中。

【思考】抛出个问题,所谓的加载到内存中,我们都知道JVM把内存分成了几大模块,那么请问是加载到哪个模块中?热点面试题,答案见文末

链接

链接中包含了三部曲,总的作用就是负责将Main.class的二进制数据合并到JRE中。

关于三部曲,其实很好理解;

首先是验证阶段,类加载器将二进制字节流加载到虚拟机中,肯定是需要进行验证的,避免危害虚拟机自身安全,而这也是验证阶段存在的价值;

接下来是准备阶段,准备阶段是正式为类变量分配内存并且设置类变量默认值的地方,比如上面HelloWorld程序中的

private static String word = "Hello World!";

注意我描述的第一个是类变量,也就是static所描述的变量,其次是默认值,也就是上面的word的默认值null,如果是数字则为0。

最后是解析阶段,解析阶段的作用主要是将常量池内的符号引用替换为直接引用的过程,解析阶段其实有点难理解,至少是比上面的两个阶段要难理解的,我这里尽量直白点;

所谓的符号引用指的是包含了类的信息、方法名、方法参数等信息的字符串,而当第一次运行时,JVM会根据这行字符串去检索到对应的方法入口,而为了下次不用再做同样的检索,在第一次运行的时候就会将符号引用替换成直接引用,这样后面就可以省去一定的消耗了;这里的直接引用其实就是指偏移量,虚拟机可以通过偏移量直接找到方法入口,不再需要做检索了。

初始化 终于来到初始化阶段了,上面我们有说到word默认值是null,是系统赋的默认值,而在初始化阶段,则是根据我们人为的初始化类变量和其他资源,比如上面的word则被我初始化成了"Hello World!"。

类执行

上面说到Main.class被加载到了Java虚拟机内存中,那么接下来便是执行的过程了。那么由谁来执行这一过程呢? 如图

a14f3715f99370e7adfed67f74f0834e.png
  • 实际上,一个Java虚拟机在运行的时候可以划分为三个子系统:类加载子系统
  • 执行引擎子系统
  • 垃圾收集子系统

很明显、很清晰,图中的类加载子系统在上面已经谈了,执行引擎子系统就是负责执行这一部分的,那么过程是怎么样的呢?

其实很简单,执行引擎会把字节码转换为机器码【what?竟然还要转换。拜托,字节码是被JVM识别的语言,机器码才是最终被操作系统识别的语言】

然后操作系统才可以真正调用,很多学或者做Java的人都听过JIT,但是都不知道具体是干嘛的,没错说的就是你。

这里终于可以解释下了,字节码转换成机器码的翻译工作使用的就是JIT(Just In Time)即时编译器(对热代码整段编译)和Java字节码解释器(一行一行解释字节码)来完成的。 这里给下JIT编译的工作流程:

JVM字节码 -> 机器无关优化 -> 中间代码 -> 机器相关优化 -> 中间代码 -> 寄存器分配器 -> 中间代码 -> 目标机器码生成器 -> 目标机器码

最后执行引擎会找到main()这个入口方法,并且执行其中的字节码指令。

最后,关于HelloWorld执行过程,基本上阐述完毕了,关于执行程序期间,JVM内存分配问题,是一个比较大的模块,我们下次再聊!!!

【思考解惑】加载阶段完成后,虚拟机会将Main.class的二进制字节流按照虚拟机所需的格式存储在方法区之中,然后在内存中实例化一个java.lang.Class类的对象,作为程序访问方法区中的这些类型数据的外部接口,实例化后的java.lang.Class类的对象也是存放在方法区中的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/566925.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java接口文档生成工具_【分享】接口文档生成工具apipost

一、为什么要写接口文档?正规的团队合作或者是项目对接,接口文档是非常重要的,一般接口文档都是通过开发人员写的。一个工整的文档显得是非重要。项目开发过程中前后端工程师有一个统一的文件进行沟通交流开发,项目维护中或者项目…

jacobi matlab程序,jacobi迭代法实验MATLAB程序数值分析

jacobi迭代法实验MATLAB程序数值分析 例1. 求线性方程组 得近似解。精确解为x*[3,2,1]’。 解:对方程进行移项就得 记为Axb,或写为xB0 xf,其中 取初始值,代入原方程组可得再将把它代入可得.反复利用这个计算过程,得到一向量序列和…

java 水印乱码,linux java程序加水印及中文乱码方案

在linux,centos环境下的,生成的带文字的水印图片在显示为方框乱码。img.setFont(new Font("宋体", Font.BOLD, 20));写中文进入图片之前必须设置字体,而且这个字体必须支持中文,否则就会出现乱码或者方框、问号等等。Bu…

resultmap拿不到数据_阿里巴巴国际站每日电商运营工作数据表格

一、日常运营工作表1.数据日报统计每天的流量数据:2.数据周报统计每周的流量数据:3.数据月报统计每月流量数据:前半部分后半部分4.P4P月数据总览统计P4P的流量数据:大图包含上面所有的方案5.P4P日数据总览6.爆款数据统计每月统计爆…

jenkins ssh 远程部署_Jenkins部署jar到远程服务器

首选得确定Jenkins的安装完整,还有插件的安装,除默认插件,此处必备插件:Publish Over SSH,SSH plugin,因为我源码在gitlab所以关于gitlab的插件我也安装了,看各自的情况选择安装。然后到系统管理…

jieba分词_Jieba.el – 在Emacs中使用jieba中文分词

jieba.el在Emacs中使用jieba中文分词众所周知, Emacs并没有内置中文分词系统, 以至于 forward-word 和 backward-word 以及 kill-word 等以单词为单位的操作只能粗暴的标点符号进行确定所谓”词汇”, (其实是中文里的句子).jieba.el 利用nodejieba对buffer中的中文句子进行分割…

java php 女生数量,萌妹子告诉你php和java如何选

原标题:萌妹子告诉你php和java如何选phpPHP 独特的语法混合了C、Java、Perl 以及 PHP 自创新的语法。它可以比 CGI 或者 Perl 更快速的执行动态网页PHP具有非常强大的功能,所有的CGI的功能PHP都能实现,而且支持几乎所有流行的数据库以及操作系…

new 结构体指针_Go:我应该用指针替代结构体的副本吗?

logo对于许多 golang 开发者来说,考虑到性能,最佳实践是系统地使用指针而非结构体副本。我们将回顾两个用例,来理解使用指针而非结构体副本的影响。1. 数据分配密集型让我们举一个简单的例子,说明何时要为使用值而共享结构体&…

localstorage存储大小_Cookie 已凉,Web 存储该这么做!

本文经授权转自公众号CSDN(ID:CSDNnews)作者 | 浪里行舟责编 | 郭芮随着移动网络的发展与演化,我们手机上现在除了有原生 App,还能跑“WebApp”——它即开即用,用完即走。一个优秀的 WebApp 甚至可以拥有和原生 App 媲美的功能和体…

php获取变量数据类型,php如何确定变量的数据类型

在php中,数据类型有:Boolean 布尔类型、Integer 整型、Float 浮点型、String 字符串、Array 数组、Object 对象、Resource 资源类型、NULL;知道一个数据的类型,能够更加有效地进行代码逻辑处理。1、使用 var_dump() 函数,可以获取…

深入理解java虚拟机 - jvm高级特性与最佳实践(第三版)_JVM虚拟机面试指南:年薪30W以上高薪岗位需求的JVM,你必须要懂!...

JVM的重要性很多人对于为什么要学JVM这个问题,他们的答案都是:因为面试。无论什么级别的Java从业者,JVM都是进阶时必须迈过的坎。不管是工作还是面试中,JVM都是必考题。如果不懂JVM的话,薪酬会非常吃亏。其实学习JVM并…

label居中_表格固定列宽时如何居中?

列宽固定居中的设置的时候,我们通常使用 p{宽度} 来指定固定的列宽,这时单元格会自动换行,换行之后是左对齐的,如何获得居中对齐呢?\begin{tabular}{|p{54pt}l|p{71pt}c|p{71pt}c|}\hline Method& Train set&T…

github新建仓库推送代码教学

之前一直用gitee,准备转到github。因为一步一步尝试。如果是新手或许文章会有帮助 点击 new 创建 拉代码 Idea 打开 复制一个 pom 文件作为 maven 管理 提交代码 不出意外的出意外,报错 点击authorize JetBrains 失败 分析问题 本质就是没有…

Linux数码管和点阵程序,随笔:python turtle绘制八段数码管和共阳极8x8led点阵

为更新而更新,为保持更新状态而更新。给学生讲解用gpiozero库控制八段管和8x8共阳极LED点阵。已经讲解了单个LED的控制,RGB彩色灯珠的控制,在讲解八段管就很容易理解,多个八段管的讲解稍微麻烦一点,然后LED点阵为了便于…

一个公网ip多少钱_一个丛书书号多少钱

点击上方“蓝字”,发现更多精彩。联系我们,有惊喜!!本站点提供:学术出书、自费出书,出版指南攻略、编审润色书稿等服务。如需了解详情,请加责编微信:xueshuzhishi出版出书&#xff0…

嵌入式linux镜像,使用Openembedded定制嵌入式Linux镜像

关键词:ARM,Linux,Openembedded作者:ByToradex秦海摘要:嵌入式设备采用Embedded Linux操作系统进行开发已经越来越成为主流,但是如何将开发完成的Linux uboot/kernel配置,以及应用程序整合到Embedded Linux镜像中以便在…

linux安装 中文乱码怎么解决方法,Linux安装GBK/GB2312程序显示乱码的五种解决方法...

不少用户在Linux系统中安装GBK或GB2312的时候遇到了乱码问题,这主要是系统默认语言是uft8所导致,对于该问题可用五种方法进行解决,接下来是小编为大家收集的Linux安装GBK/GB2312程序显示乱码的五种解决方法,希望能帮到大家。Linux…

linux 设备驱动总结,linux设备驱动归纳总结(三):3面向对象思想和lseek

linux设备驱动归纳总结(三):3.设备驱动面向对象思想和lseek的实现一、结构体structfile和struct inode在之前写的函数,全部是定义了一些零散的全局变量。有没有办法整合成到一个结构体当中?这样的话,看起来和用起来都比较方便。接…

idea junit 测试看不到控制台报错信息_高手都这么给 Spring MVC 做单元测试!

本章节主要讲解以下两部分内容:1、Mock 测试简介2、测试用例演示一、Mock 测试简介1、什么是 mock 测试在测试过程中,对于某些不容易构造或者不容易获取的对象,用一个「虚拟的对象」来创建以便测试的测试方法,就是 「mock 测试」在…

windows查询每个线程占用的内存_C#多线程

一、基本概念1、进程首先打开任务管理器,查看当前运行的进程:从任务管理器里面可以看到当前所有正在运行的进程。那么究竟什么是进程呢?进程(Process)是Windows系统中的一个基本概念,它包含着一个运行程序所…