对于写代码的人都知道,我们一般的程序编译过程都有语法分析、词法分析等一系列检查操作,然后生成对应的机器码或者字节码。对于C++来说,其编译过程可使用下图表示
![深入理解JVM—字节码编译机制 - 一线天色 天宇星辰 - 一线天色 天宇星辰](http://img7.ph.126.net/FfE8Wi6Ghkgjhro9dAea5Q==/1040331513939890447.jpg)
当然我们知道还有一部分语言是使用解释器工作的,其标准流程如下:
![深入理解JVM—字节码编译机制 - 一线天色 天宇星辰 - 一线天色 天宇星辰](http://img4.ph.126.net/FmJRvDGm-cTE86LPoPvGkA==/2748884622573575654.jpg)
我们大学的时候,很多老师会告诉我们,
java 是一门解释性语言,但是 Java 笔传统解释器执行做了一定的优化,它不是对源码文件直接的解释,而是对通过优化后生成的字节码文件作解释操作,其流程如下:
![深入理解JVM—字节码编译机制 - 一线天色 天宇星辰 - 一线天色 天宇星辰](http://img6.ph.126.net/_71OXr3p-857rhu4CJrIIQ==/2866259687861917976.jpg)
Java
执行过程中首先对符合 Java 语言规范和虚拟机规范的源码进行一次编译操作,生成机器无关性的符合 JVM 规范的字节码文件,然后在 JVM 运行时再做解释操作,进而完成 Java 代码从源码到机器指令的执行,其流程图如下:
![深入理解JVM—字节码编译机制 - 一线天色 天宇星辰 - 一线天色 天宇星辰](http://img1.ph.126.net/MNAIftDyf1B-mk3VKzfJRQ==/2720174174949088301.jpg)
但实际上,
Java 并不是一门纯粹的解释执行的语言,从 JDK1.2 之后, JVM 在运行之前会对字节码和运行环境做一次检查,如果运行的 JVM 是 Client 模式,则继续使用解释执行模式,如果是 Server 模式, CPU 超过 4 个逻辑核心,内存超过 4GB 则 JVM 会选择性的将频率使用较高的字节码启用 JIT 编译,编译后直接生成机器相关的字节码,这样下次再次执行这段代码的时候效率就会高很多。从 JDK1.2 (又称 java2 )之后,其流程可用下图表示:
![深入理解JVM—字节码编译机制 - 一线天色 天宇星辰 - 一线天色 天宇星辰](http://img6.ph.126.net/rth5JGkFAdFjgjVFdhpYFg==/1027102190034496056.jpg)
对于字节码的处理分成了
2 个分支,对于一部分 代码使用 JIT 编译器生成对应的机器代码,而对于另一部分则继续使用解释执行的方式。下面我们来逐个看一下每个步骤JVM所做的事情。
首先我们要提到的肯定是生成机器无关性的字节码文件爱你,那字节码是如何生成的呢?
我们都知道是通过Java的编译器编译生成。Java编译器的主要任务就是将符合java 语言规范的源码编译为符合java虚拟机规范的字节码文件,如果输入的Java源码不符合规则,则报告错误。
平时我们最熟悉的莫过于Sun公司的JDK。Sun JDK中的源码编译器是javac,该编译器是使用java 编写的,从某种程度上实现了java的自举(bootstrap),而真正实现自举还得结合Java实现的JVM。
其实我们实际工作中使用的最多的ECJ(Eclipse Compiler for Java)的编译器
编译器在执行过程中其实就是根据Java的语法规则,将语言中隐含的结构表现出来。
Javac的工作流程如下图所示:
![深入理解JVM—字节码编译机制 - 一线天色 天宇星辰 - 一线天色 天宇星辰](http://img6.ph.126.net/65huv3-mPHZbFxFItBR5Dg==/623748548408121476.jpg)
通过解析与输入到符号表,注解处理、分析与代码生成几个步骤。其中分析与代码生成包含以下几方面:
1、 属性标注与检查(Attr and Check)
2、 数据流分析(Flow)
3、 泛型转换(TransType)(将泛型转换为实际的裸体类型(raw type))
4、 解除语法糖(Lower)
5、 生成字节码文件(Gen)
而解析包含词法分析和语法分析,词法分析主要是通过com.sun.tools.javac.parser.Scanner、手写的ad-hoc方式构造的词法分析器将字符序列转换为标准词法的token序列。语法分析是使用com.sun.tools.javac.parser.Parser、递归下降和运算符优先程式的语法分析器根据语法将词法分析器生成的token序列生成抽象语法树。语法分析后所有的步骤都在抽象语法树上进行。下图是词法分析和语法分析的一个示意图。
int y = x + 1;对应的词法分析如下:
![深入理解JVM—字节码编译机制 - 一线天色 天宇星辰 - 一线天色 天宇星辰](http://img3.ph.126.net/nwKXyJtIWrcp2BjjADki9w==/1349953988321610803.jpg)
![深入理解JVM—字节码编译机制 - 一线天色 天宇星辰 - 一线天色 天宇星辰](http://img3.ph.126.net/miS50zlEU79FKfxEyctpfg==/66428094521019793.jpg)
语法分析完毕后编译器将通过
com.sun.tools.javac.comp.Enter 将每个编译单元的抽象语法树顶节点放在待处理列表中,然后逐个处理列表中的节点。处理完成的类符号被输入到外围作用域的符号表中。然后逐个确定类的参数 ( 针对泛型类型 ) 、超类型和接口、根据需要添加默认构造器,将类中出现的符号舒服到自身的符号表中、分析和校验代码中的注解 (annotation) 。完成类定以前的代码如下:
package com.yhj.test; /** * @Described:测试用例 * @author YHJ create at 2012-3-9 上午10:25:18 * @FileNmae com.yhj.test.TestCase.java */ public class TestCase { } |
完成类定义以后的代码如下
package com.yhj.test; /** * @Described:测试用例 * @author YHJ create at 2012-3-9 上午10:25:18 * @FileNmae com.yhj.test.TestCase.java */ public class TestCase { public TestCase() { super(); } } |
我们可以很清楚的看到添加了一个默认的构造器。
接下来要做的事情是注解处理,是通过com.sun.toolsjavac.processing.JavaProcessingEnviroment进行处理的。我们都知道注解是JDK1.6以后新加的特性,也就是支持注解的最低JDK版本是JDK6,通过注解处理,可以读取到语法树中的所有元素,包含注释,可以更改类类型的定义,可以创建新的类型。。。而这一系列操作又可以重新的解析和写入符号表,如下图所示:
![深入理解JVM—字节码编译机制 - 一线天色 天宇星辰 - 一线天色 天宇星辰](http://img0.ph.126.net/5JkokcH-OGg5SQgMEHMfvw==/2394789101871565970.jpg)
如下图所示,代码直接处理前如下:
![深入理解JVM—字节码编译机制 - 一线天色 天宇星辰 - 一线天色 天宇星辰](http://img9.ph.126.net/xKlCrZGc2wRFl9XOumQ3Jg==/1043427738683709509.jpg)
代码注解处理后如下图所示
![深入理解JVM—字节码编译机制 - 一线天色 天宇星辰 - 一线天色 天宇星辰](http://img4.ph.126.net/if3iKm2allz2JGuJ9TN4xg==/2522297266321499004.jpg)
注解处理完成,接着要做的事情就是标注和检查
(Attr and check) ,主要通过类 com.sun.tools.javac.comp.Attr 和 com.sun.tools.javac.comp.Check 完成,它是语义分析的一个步骤。这个过程主要做以下事情:
1、 将语法树中的名字、表达式等元素与变量、方法、类型等联系到一起
2、 检查变量使用前是否已经声明
3、 推导泛型方法的类型参数
4、 检查类型匹配
5、 进行常量折叠
如下图所示,标注前的代码如下:
![深入理解JVM—字节码编译机制 - 一线天色 天宇星辰 - 一线天色 天宇星辰](http://img5.ph.126.net/br2GAt8KfFppevdf4ijrWw==/998110267433294955.jpg)
标注后的代码形态如下:
![深入理解JVM—字节码编译机制 - 一线天色 天宇星辰 - 一线天色 天宇星辰](http://img7.ph.126.net/ufds902_niPnmfu5-eoKGw==/112027040748143477.jpg)
标注完成之后进行数据流分析,主要是通过类
com.sun.tools.javac.comp.Flow 这个类来完成的,它也是语义分析的一个步骤,主要做以下几件事情:1、 检查所有语句是否都可达
2、 检查所有的checked exception都被捕获或者抛出
3、 检查变量的确定性赋值
a) 所有局部变量在使用前都必须有确定的赋值
b) 有返回值的方法必须有确定性的返回值
4、 检查变量的确定性不重复赋值
a) 保证final类型不能重复赋值
接下来要做的事情就是转换类型(TransTypes),是通过com.sun.tools.javac.comp.TransTypes处理的,这也是解除语法糖的一个重要步骤,如想泛型转换为普通的Java代码同时插入必要的类型转换代码,如下图所示,转换类型前代码如下:
![深入理解JVM—字节码编译机制 - 一线天色 天宇星辰 - 一线天色 天宇星辰](http://img9.ph.126.net/O1CmXTDy5uGhaH1OCNJjRQ==/591660401063107689.jpg)
类型转换后代码如下:
![深入理解JVM—字节码编译机制 - 一线天色 天宇星辰 - 一线天色 天宇星辰](http://img1.ph.126.net/Y0QP7biPrBYwA_VLuKsVSA==/575053377437174871.jpg)
解除语法糖是通过
com.sun.tools.javac.comp.Lower 来处理的,主要用于以下一些方面1、 消除无用代码
a) 满足以下条件的被认为条件编译的无用代码
i. If语句的条件表达式是常量表达式的
ii. 表达式的值为false的then无用代码和为true条件下的else无用代码块
2、 将含有语法糖的语法树改写为含有简单语言结构的语法树
a) 内部类(具体的内部类和匿名内部类)
b) 字面常量
c) 断言
d) 自动拆装箱
e) Foreach循环
f) Enm类型的switch语句
g) String类型的switch(Java7新支持的语法)
h) 。。。
如下图所示,这是Lower之前的代码
![深入理解JVM—字节码编译机制 - 一线天色 天宇星辰 - 一线天色 天宇星辰](http://img7.ph.126.net/Ld9r6Pp04B6p5inruDDkvw==/2847119389445600157.jpg)
Lower
之后如下图所示
![深入理解JVM—字节码编译机制 - 一线天色 天宇星辰 - 一线天色 天宇星辰](http://img7.ph.126.net/U6xbSqZ4_LBtiNzAIkdfhQ==/2612650733845601367.jpg)
Lower
之前的另外一段代码
![深入理解JVM—字节码编译机制 - 一线天色 天宇星辰 - 一线天色 天宇星辰](http://img7.ph.126.net/Ld9r6Pp04B6p5inruDDkvw==/2847119389445600157.jpg)
Lower
之后的代码
![深入理解JVM—字节码编译机制 - 一线天色 天宇星辰 - 一线天色 天宇星辰](http://img8.ph.126.net/t5b_Izi1-m8Wj-i1m_av3A==/2618843183333246508.jpg)
解除语法糖之后就开始生成字节码文件,是通过
com.sun.tools.javac.jvm.Gen 生成的,主要实现以下功能1、 将实例成员初始化收集器收集到构造器中的<init>()
2、 将静态成员初始化器收集为<clinit>()
3、 将抽象语法树生成字节码
a) 后续遍历语法树
b) 进行少量的代码转换
i. String中的+被生产为StringBuilder操作
ii. X++/x—在条件允许时被优化为++x/--x
4、 从符号表生成Class文件
a) 生成字节码文件的结构信息
b) 生成元数据(包括常量池)
其流程如下
![深入理解JVM—字节码编译机制 - 一线天色 天宇星辰 - 一线天色 天宇星辰](http://img7.ph.126.net/bPS9FvaFC4FOGZ00vc1nOA==/2593791910406002917.jpg)
执行过程如下图所示
![深入理解JVM—字节码编译机制 - 一线天色 天宇星辰 - 一线天色 天宇星辰](http://img6.ph.126.net/bk_BWVJzBjkzorGtjCG9Uw==/2746914297736597106.jpg)
至于具体是如何执行的?字节码的结构又是怎样的?