java执行class找不到main函数_你所不知道的HelloWorld背后的执行原理
专注于Java领域优质技术,欢迎关注
作者:饭谈编程
【今日最佳】对于程序员而言,所谓的二八定律指的是 花百分之八十的时间去学习日常研发中不常见的那百分之二十的原理。
据说阿里某程序员对书法十分感兴趣,退休后决定在这方面有所建树。于是花重金购买了上等的文房四宝。
一日,饭后突生雅兴,一番磨墨拟纸,并点上了上好的檀香,颇有王羲之风范,又具颜真卿气势,定神片刻,泼墨挥毫,郑重地写下一行字:hello world。
![](/assets/blank.gif)
当然了,这是个专属程序员的段子哈哈哈。
那么问题来了,写了这么久的Hello World,大家确定自己了解自己写的东西背后是什么原理吗?(o≖◡≖)
【给出2分钟,该知识点涉及到了Java程序执行流程,包括编译、加载和执行,你是否能够理清呢?】
接下来进入严肃时间 (@ ̄ー ̄@)
与众不同的Hello World
![](/assets/blank.gif)
整个代码的执行过程可以分为三个阶段:
- 代码编译
- 类加载
- 类执行
代码编译
代码编译的作用就是将我们编写的 Main.java文件转化为Main.class文件,.class在这里又被称为字节码文件,打开就是一堆的火星文【反正就是看不懂】,在这里我们可以将编译的过程看作生产JVM原料的过程,使用的工具就是jdk提供的工具javac。 大致流程如下:
- 词法分析,即将源代码的字符流转变为Token集的过程。白话文描述下,就是在我们实际编程中,单个字符是最小单位,而实际上在编程过程中,标记才是最小单位,如关键字、变量名、字面量、运算符等都可以成为Token,貌似还是有点蒙蔽,举个例子(>﹏<),比如整型int在我们编程中它就是三个字符组成的,就是i、n、t 三个字符,而在编译过程中它就是一个Token,不可拆分。
这个过程对我们来说其实是完全屏蔽的,但是实际上它是现代经典编译原理的 套路,词法分析也是为了给后面编译做准备的】
- 语法分析,通过词法分析拿到Token集后,下一步就是构建抽象语法树了,所谓的抽象语法树其实就是一种用来描述程序代码语法结构的树形表示方式,其中语法树的每一个节点都代表着程序代码中的一个语法结构,如包、类型、修饰符、运算符等。
在我们眼中,Main.java已经可以清晰理解到底写的是什么东西了,但是对于JVM来说还是一脸懵逼的,所以才需要构建成语法树,在这一步后就不会再对源码文件进行操作了,后续的操作都建立在抽象语法树上
- 填充符号表,符号表是由一组符号地址和符号信息构成的表格,这个表格在编译的不同阶段都会被用到,如在目标代码生成阶段,会对符号名进行地址分配,而符号表就是地址分配的依据。
- 语义分析,语义分析阶段也可以说是语义检测阶段,上面说到语法分析会构建一棵语法树,那么这棵语法树是否是正确合理的,就由语义分析来做了,语义分析会通过标注检查和数据及控制流分析检查两步入手,在生成字节码的最后一步信息把关。
- 字节码生成,这是javac编译过程的最后一个阶段了,字节码生成阶段并不只是简简单单的将前面各个步骤生成的信息转化成字节码然后放入磁盘中,还进行了少量的代码添加和转换工作,如我们自己重载的构造函数。
编译期到这里就结束了,那么由谁来将这些原料传输给JVM虚拟机呢?这个时候就要看看类加载的过程了。
类加载
类加载简单来说就是将由类加载器将编译后的字节码文件【Main.class】加载到虚拟机中 ,那么自然而然的,要先介绍下四种类加载器
说说四种类加载器
![](/assets/blank.gif)
可以从上图中看出,类加载器可以分为四种,而第四种是由我们自己实现的,那么其他三种由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。
知道了类加载器后,那接下来总要了解下类加载器怎么加载的吧?
说说类加载的过程
![](/assets/blank.gif)
网上找了张图片,简单明了。虽说是简单明了,不过确实异常重要的,因为是面试热点(✿◡‿◡)
加载
其实就是上文说到的系统类加载器 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虚拟机内存中,那么接下来便是执行的过程了。那么由谁来执行这一过程呢? 如图
![](/assets/blank.gif)
- 实际上,一个Java虚拟机在运行的时候可以划分为三个子系统:类加载子系统
- 执行引擎子系统
- 垃圾收集子系统
很明显、很清晰,图中的类加载子系统在上面已经谈了,执行引擎子系统就是负责执行这一部分的,那么过程是怎么样的呢?
其实很简单,执行引擎会把字节码转换为机器码【what?竟然还要转换。拜托,字节码是被JVM识别的语言,机器码才是最终被操作系统识别的语言】
然后操作系统才可以真正调用,很多学或者做Java的人都听过JIT,但是都不知道具体是干嘛的,没错说的就是你。
这里终于可以解释下了,字节码转换成机器码的翻译工作使用的就是JIT(Just In Time)即时编译器(对热代码整段编译)和Java字节码解释器(一行一行解释字节码)来完成的。 这里给下JIT编译的工作流程:
JVM字节码 -> 机器无关优化 -> 中间代码 -> 机器相关优化 -> 中间代码 -> 寄存器分配器 -> 中间代码 -> 目标机器码生成器 -> 目标机器码
最后执行引擎会找到main()这个入口方法,并且执行其中的字节码指令。
最后,关于HelloWorld执行过程,基本上阐述完毕了,关于执行程序期间,JVM内存分配问题,是一个比较大的模块,我们下次再聊!!!
【思考解惑】加载阶段完成后,虚拟机会将Main.class的二进制字节流按照虚拟机所需的格式存储在方法区之中,然后在内存中实例化一个java.lang.Class类的对象,作为程序访问方法区中的这些类型数据的外部接口,实例化后的java.lang.Class类的对象也是存放在方法区中的。
java执行class找不到main函数_你所不知道的HelloWorld背后的执行原理相关推荐
- python 函数调用 不允许关键字参数_你所不知道的Python|函数参数的演进之路
原标题:你所不知道的Python|函数参数的演进之路 函数参数处理机制是Python中一个非常重要的知识点,随着Python的演进,参数处理机制的灵活性和丰富性也在不断增加,使得我们不仅可以写出简化的 ...
- java strim性能_你所不知道的Java性能优化之String!
Java性能优化之String字符串优化 1.字符串对象及其特点 Java中八大基本数据类型没有String类型,因为String类型是Java对char数组的进一步封装. String类的实现主要由 ...
- VLOOKUP 函数,你所不知道的用法
相信不少人看到标题,立即嗤之以鼻,VLOOKUP 谁不会?是的,大家都会,但用的好的人不多.相信我,这篇文章一定可以算得上通俗易懂,又有深度的一篇文章,熟练掌握本文所讲内容,一定会在日常 Excel ...
- java 同步原语_你所不知道的有关Java 和Scala中的同步问题
在实际应用中所有的服务端程序都需要在多线程之间进行某种同步.大多数同步已经有框架完成了,比如我们的web服务器,DB客户端和消息框架.Java和Scala提供了大量的组件用来实现稳定的多线程程序.包括 ...
- Java中static代码块,main函数,构造函数运行顺序
1.Java中static代码块,main函数,构造函数运行顺序如下: 答:static代码块是主动执行的,因此static代码块先执行,然后是执行构造函数,最后是Main函数. 如下例子: publ ...
- 【JAVA】错误: 找不到或无法加载主类 HelloWorld
[JAVA]错误: 找不到或无法加载主类 HelloWorld 1. 问题描述 在本地使用记事本创建文件并且使用java和javac来编译与执行时出现错误 命令以及提示如下: root>java ...
- java main函数_都知道Java程序的入口方法是main,那你知道为什么是main方法吗?
我们都知道Java的入口方法是main函数,下面这段代码就是Java中非常经典的Hello World代码: 我们通过Java提供的工具进行编译执行: 通过上面的代码我们知道Java入口方法的几个要求 ...
- java main函数_一行JAVA代码如何运行起来?
在程序员的世界中,你总会听到一句"PHP是世界上最好的语言"的调侃.然而在你进入软件程序开发之后,你会发现即使开发语言千千万,最盛行的还是JAVA.从淘宝的技术变迁中我们可以见一些 ...
- java中的方法 net.中的函数_.Net转Java.01.从Main(main)函数说起
在C#中,main函数的签名可以有四种 static void Main(string[] args) static void Main() static int Main(string[] args ...
最新文章
- CV算法复现(分类算法2/6):AlexNet(2012年 Hinton组)
- mysql 单标递归_MySql整理篇之递归
- TIOBE12月榜单:Java重回第二,Python有望四连冠年度语言
- 虚拟化方案应用场景及优劣
- android handler作用,3.2.4 Handler的作用
- Grunt上手指南(转)
- Algorithm:递归思想及实例分析
- ADO.NET与抽水的故事 系列三——抽水机—Command
- [转载] 像 IDE 一样使用 vim
- C Primer Plus(第6版)第一章复习题答案
- 我国芯片各细分领域龙头名单
- laravel 使用 vue 和 element
- [科研][转载] 对科研思维方法的整理(节选) from 玉泉老博
- Java对大文件MD5解密详解
- 关于为什么频宽越大传输越快 、 频率越高传输距离越短
- 施工现场资料员15个常见问题处理流程!
- 浏览器翻译插件 沙拉查词;图片翻译;pdf 阅读器软件、pdf翻译工具
- 面试题:Lucene、Solr、ElasticSearch
- 百度被黑了,哈哈!!
- 无人值守污水处理控制系统,西门子200PLC和显控触摸屏编写的智能污水处理控制系统
热门文章
- SpringBoot使用Easypoi导出excel示例
- springboot+shiro:ShiroConfiguration配置
- ssm框架sql换成MySQL_搭建ssm框架,可实现登录和数据展示以及增删改查
- c++二叉树的层序遍历_leetcode 103. 二叉树的锯齿形层序遍历
- Android JNI Attempt to remove non-JNI local reference, dumping thread
- Maven学习(五)————依赖的特性辨析
- 关于面向对象以及三大特征的解释
- python内存管理机制_python内存管理机制
- linux安装版本的python,linux安装python各种版本.md
- 改进版的CBOW模型