不知你们有没有思考过,当咱们使用IDE写了一个Demo类,并执行main函数打印 hello world时都经历了哪些流程么? 想经过这篇文章来分析分析Java的执行流程,或者换句话说想聊聊Java的编译期与运行期的流程。html
public class MyApp {
public static void main(String[] args) {
System.out.println("hello world");
}
}
复制代码
假如咱们写了一个MyApp.java,并要打印‘hello world’ 那它须要通过哪些步骤?java
第一步:compile编程
经过编译器进行编译,从Java源码 ---> Java 字节码安全
这个编译器则是jdk 里的javac 编译器,咱们只需 javac MyApp.java 便可以编译该源码,javac 编译器位于jdk --> bin -->javacbash
第二步:load and execute架构
加载java 字节码并执行oracle
能够经过jdk 里的java命令运行java字节码,咱们只需 java MyApp.class 便可加载并执行该字节码,当运行java命令时,JRE将与您指定的类一块儿加载。而后,执行该类的主要方法。jvm
java命令位于jdk --> bin -->java。编程语言
上面只是大概讲了运行一个java程序的流程,下面再从编译期以及运行期的角度在剖析一下细节。函数
编译器(compiler)是一种计算机程序,它会将某种编程语言写成的源代码(原始语言)转换成另外一种编程语言(目标语言)。
编译期都作了什么?从咱们使用者角度看无非就是把源代码编译成了可被虚拟机执行的字节码,可是从平台(编译器)角度看,它所经历的流程还很多。 毕竟总不能给你什么以.java为后缀的文件都进行编译吧,须要有各类校验解析步骤
词法语法分析
词法分析
是指把源代码的字符流转为标记(Token)集合,标记(Token)是编译阶段的最小单元,字符则是编程阶段源码的最小单元。
好比,int i = 0由4个标记构成分别是「int,i,=,0」编译器只认识这些标记,词法分析过程就是识别一个个标记的过程
语法分析
则是把生成的标记集合 构成一个语法树,每一个节点表明程序代码中的语法结构,如包,类型,修饰符,运算符等等。
填充符号表
经过了上面的词义语义分析以后咱们须要把数据存起来,以供后续流程使用,编译器会以key-value的形式存储数据,以符号地址为key符号信息为value,具体形式没作限制能够是树状符号表或者有序符号表等。
在语义分析中,根据符号表所登记的内容 语义检查和产生中间代码,在目标代码生成阶段,当对符号表进行地址分配时,该符号表是检查的依据。
注解与普通的Java代码同样,是在运行期间发挥做用的。咱们能够把它看作是一组编译器的插件,在这些插件里面,能够读取、修改、添加抽象语法树中的任意元素。
若是这些插件在处理注解期间对语法树进行了修改,编译器将回到解析及填充符号表的过程从新处理,直到全部插入式注解处理器都没有再对语法树进行修改成止。
换句话说当咱们处理注解时若是修改了语法树的话会从新执行分析以及符号填充过程,把注解也填充进来,直处处理完全部注解。
语法分析以及处理注解以后,编译器得到了程序代码的抽象语法树,语法树能表示一个结构正确的源程序的抽象,但没法保证源程序是符合逻辑的。
说白了,语法树上的内容单个来讲是合法的可是结合到上下文语义则未必是合法的。
好比定义了两个变量
int a = 1;
boolean b = false;
int c = a + b
复制代码
以上 都能构成结构正确的语法树,可是根据语义分析以后编译是通不过,Java语言中是不合乎逻辑的。
Java 中最经常使用的语法糖主要有泛型、变长参数、条件编译、自动拆装箱、内部类等。虚拟机并不支持这些语法,它们在编译阶段就被还原回了简单的基础语法结构,这个过程成为解语法糖。
换句话说,不论你是否使用Java的语法糖,最终到jvm哪里的时候都是同样的,jvm不支持语法糖,因此须要编译阶段解语法糖,语法糖的初衷是用来提高开发效率,而不是代码性能。
字节码生成是Javac编译过程的最后一个阶段,在Javac源码里面由com.sun.tools.javac. jvm.Gen类来完成。字节码生成阶段前面各个步骤所生成的信息(语法树、符号表)转化成字节码写到磁盘中,主要工做就是把语法树和符号表加工成字节码文件。
java的运行期主要是处理编译器产生的字节码,包括加载与执行。
java提供类加载器把虚拟机外部的字节码资源载入到虚拟机的运行时环境(主要是指虚拟机的方法区)
并提供字节码验证器来保证载入的字节码是安全合法的,对程序没有危害的。
加载器 (Class Loader)
当字节码还没被类加载器加载以前它目前还处于虚拟机外部存储空间里,要想执行它须要经过类加载器来加载到虚拟机的运行时内存空间里。关于类加载器不太想过多扩展,有兴趣珂查阅相关书籍资料。
常见类加载器有:
总之,加载器的任务就是把字节码资源载入到虚拟机运行时环境里
字节码验证 (Bytecode Verifier)
当类加载器将新加载的字节码呈现给虚拟机时,首先由验证器来检查验证这些字节码。验证程序检查指令是否没法执行明显有害的操做。除系统类以外的全部类都须要通过验证。也可使用命令-noverify选项来停用验证。
字节码验证器主要验证以下几项:
总之,验证器的任务就是保证加载器载入的字节码资源的安全性,正确性
解释器
解释器(interpreter),是一种计算机程序,可以把高级编程语言一行一行解释 运行。
划重点:一行一行运行,说白了就是效率低
解释器每次运行程序时都要一行一行先转成另外一种语言再做运行,所以解释器的程序运行速度比较缓慢。它不会一次把整段代码翻译出来,而是每翻译一行程序叙述就马上运行,而后再翻译下一行,再运行,如此不停地进行下去。
JIT编译器
即时编译(Just-in-time compilation)是一种提升程序运行效率的方法。一般,程序在执行前所有被翻译为机器码。
Java最初的版本没有JIT编译器,彻底靠解释器来运行的,可是为了提高性能便引入了JIT编译器,
重点说明:当咱们说编译的时候基本上指的是上面的从源码到字节码的编译过程,而不是指JIT编译器
JIT编译器工做阶段基本是java程序运行期的最后阶段了,它的工做是将加载的字节码转换为机器码。当使用JIT编译器时,硬件能够执行JIT编译器生成的机器码,而不是让JVM重复解释执行相同的字节码致使相对冗长的翻译过程。 这样能够带来执行速度的性能提高。
何时触发即时编译?
上面两个条件又叫作热点代码,至于如何界定这个屡次或者热点,Java提供了两种策略:
热点探测: 虚拟机按期检查线程的栈顶,若是某个方法常常出如今栈顶 则推断为热点代码
计数器: 统计方法的调用次数,维护一个计数器列表
基于计数器来推断热点代码是HotSpot虚拟机采用的策略
一般状况下,解释器和JIT编译器混合配合工做,而不是单独工做,这样能够作到互补提高总体性能。HotSpot 虚拟机的解释器JIT编译器架构以下图所示:
HotSpot虚拟机中内置了两个即时编译器,分别称为Client Compiler和Server Compiler,或者简称为C1编译器和C2编译器,默认采用解释器与其中一个编译器直接配合的方式工做,程序使用哪一个编译器,取决于虚拟机运行的模式,用户也可使用“-client”或“-server”参数去强制指定虚拟机运行在Client模式或Server模式。
java 程序是如何运行的?
首先须要把源代码(高级语言) 编译成虚拟机可执行的语言(字节码)
其次,须要把字节码解释运行后者编译成操做系统级别的机器语言,用于执行函数调用(System call)
Java是如何作到平台独立的?
主要是由于字节码技术。咱们能够把在Windows系统上编译生成的字节码文件放在Linux系统上去执行,反之亦可。
虚拟机不在意你是那个操做系统生成的字节码文件,他只在意加载的这个.class字节码文件是不是正确的,安全的。
虽然Java语言是平台独立的,可是虚拟机不行。每种操做系统都要下载对应的虚拟机,这主要是因为它最终调用的函数库以及线程模型不一样。
参考:
1.docs.oracle.com/javase/spec…. 2.深刻理解Java虚拟机