听说阿里某程序员对书法十分感兴趣,退休后决定在这方面有所建树。因而花重金购买了上等的文房四宝。java
一日,饭后突生雅兴,一番磨墨拟纸,并点上了上好的檀香,很有王羲之风范,又具颜真卿气势,定神片刻,泼墨挥毫,郑重地写下一行字:hello world。程序员
固然了,这是个专属程序员的段子哈哈哈。面试
那么问题来了,写了这么久的Hello World,你们肯定本身了解本身写的东西背后是什么原理吗?(o≖◡≖)编程
【给出2分钟,该知识点涉及到了Java程序执行流程,包括编译、加载和执行,你是否可以理清呢?】安全
接下来进入严肃时间 (@ ̄ー ̄@)函数
public class Main { private static String word = "Hello World!"; public static void main(String[] args) { new Main().say(); } private void say() { System.out.println(word); } }
整个代码的执行过程能够分为三个阶段:工具
代码编译的做用就是将咱们编写的 Main.java文件转化为Main.class文件,.class在这里又被称为字节码文件,打开就是一堆的火星文【反正就是看不懂】,在这里咱们能够将编译的过程看做生产JVM原料的过程,使用的工具就是jdk提供的工具javac。 大体流程以下:优化
这个过程对咱们来讲实际上是彻底屏蔽的,可是实际上它是现代经典编译原理的 套路,词法分析也是为了给后面编译作准备的】操作系统
在咱们眼中,Main.java已经能够清晰理解到底写的是什么东西了,可是对于JVM来讲仍是一脸懵逼的,因此才须要构建成语法树,在这一步后就不会再对源码文件进行操做了,后续的操做都创建在抽象语法树上翻译
填充符号表,符号表是由一组符号地址和符号信息构成的表格,这个表格在编译的不一样阶段都会被用到,如在目标代码生成阶段,会对符号名进行地址分配,而符号表就是地址分配的依据。
语义分析,语义分析阶段也能够说是语义检测阶段,上面说到语法分析会构建一棵语法树,那么这棵语法树是不是正确合理的,就由语义分析来作了,语义分析会经过标注检查和数据及控制流分析检查两步入手,在生成字节码的最后一步信息把关。
字节码生成,这是javac编译过程的最后一个阶段了,字节码生成阶段并不仅是简简单单的将前面各个步骤生成的信息转化成字节码而后放入磁盘中,还进行了少许的代码添加和转换工做,如咱们本身重载的构造函数。
编译期到这里就结束了,那么由谁来将这些原料传输给JVM虚拟机呢?这个时候就要看看类加载的过程了。
类加载简单来讲就是将由类加载器将编译后的字节码文件【Main.class】加载到虚拟机中 ,那么天然而然的,要先介绍下四种类加载器
说说四种类加载器
能够从上图中看出,类加载器能够分为四种,而第四种是由咱们本身实现的,那么其余三种由JVM提供的类加载在咱们启动该Main程序的过程当中起到了什么做用呢?
首先说说启动类加载器 Bootstrap ClassLoader ,启动类加载器的做用主要是加载 %JAVA_HOME%\jre\lib\rt.jar 类库,将其加载到虚拟机内存中,那么rt.jar类库到底有什么做用呢?rt.jar下包含了Java的基础类库,也就是Java doc里面看到的全部的类的class文件,感兴趣的朋友能够本身打开目录看下。
其次是扩展类加载器 Extension ClassLoader ,扩展类加载器的做用主要是负责加载JAVA_HOME\jre\lib\ext目录下的全部类库,主要是载入扩展包。
再者是系统类加载器 Application ClassLoader, 也称之为应用程序类加载器,负责加载用户类路径(也就是咱们配置的CLASSPATH)上所指定的类库,是应用程序中默认的类加载。
看完以上三个类加载器的简单描述过程,是否是有种终于知道了咱们配置的jdk环境的最终做用了吧,是的,就是让类加载器识别到后加载各类类库。
那么问题来了?是哪一个类加载器加载了咱们的Hello World程序呢?是的,就是应用程序中默认的类加载器 Application ClassLoader。
知道了类加载器后,那接下来总要了解下类加载器怎么加载的吧?
说说类加载的过程
网上找了张图片,简单明了。虽然说是简单明了,不过确实异常重要的,由于是面试热点(✿◡‿◡)
其实就是上文说到的系统类加载器 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虚拟机内存中,那么接下来即是执行的过程了。那么由谁来执行这一过程呢?
实际上,一个Java虚拟机在运行的时候能够划分为三个子系统:
很明显、很清晰,图中的类加载子系统在上面已经谈了,执行引擎子系统就是负责执行这一部分的,那么过程是怎么样的呢?
其实很简单,执行引擎会把字节码转换为机器码【what?居然还要转换。拜托<(ˉ^ˉ)>,字节码是被JVM识别的语言,字节码才是最终被操做系统识别的语言】
而后操做系统才能够真正调用,不少学或者作Java的人都听过JIT,可是都不知道具体是干吗的,没错说的就是你。
这里终于能够解释下了,字节码转换成机器码的翻译工做使用的就是JIT(Just In Time)即时编译器(对热代码整段编译)和Java字节码解释器(一行一行解释字节码)来完成的。 这里给下JIT编译的工做流程:
JVM字节码 -> 机器无关优化 -> 中间代码 -> 机器相关优化 -> 中间代码 -> 寄存器分配器 -> 中间代码 -> 目标机器码生成器 -> 目标机器码
最后执行引擎会找到main()这个入口方法,而且执行其中的字节码指令。
**【思考解惑】**加载阶段完成后,虚拟机会将Main.class的二进制字节流按照虚拟机所需的格式存储在方法区之中,而后在内存中实例化一个java.lang.Class类的对象,做为程序访问方法区中的这些类型数据的外部接口,实例化后的java.lang.Class类的对象也是存放在方法区中的。