JavaJVM_编译器
目录
*.java 文件要首先被编译成 *.class 文件才能被 JVM 认识,这部分的工作主要由 Javac 来完成,类似于 Javac 这样的我们称之为前端编译器;
但是 *.class 文件也不是机器语言,怎么才能让机器识别呢?就需要 JVM 将 *.class 文件编译成机器码,这部分工作由JIT 编译器完成;
除了这两种编译器,还有一种直接把 *.java 文件编译成本地机器码的编译器,我们称之AOT 编译器。
1. javac编译过程
这三个步骤之间的关系和交互顺序如下图所示,可以看到
如果注解处理器在处理注解期间对语法树进行了修改
,编译器将回到解析和填充符号表的过程进行重新处理,直到注解处理器没有再对语法树进行修改为止。
- 词法分析:是
将源代码的字符流转变为标记(Token)集合
,单个字符是程序编写过程的最小元素
,而标记则是编译过程的最小元素
,关键字、变量名、字面量、运算符都可以看成标记
。 - 语法分析:是
根据Token序列构造抽象语法树
的过程,抽象语法树(Abstract Syntax Tree,AST)是一种用来描述程序代码语法结构的树形表示方式
,语法树的每一个节点都代表着程序代码中的语法结构(Construct),例如包、类型、修饰符、运算符、接口、返回值甚至代码注释都可以是一个语法结构。 - **符号表(Symbol Table)**是由一组
符号地址和符号信息
构成的表格,读者可以把它想象成哈希表中K-V值对的形式(实际上符号表不一定是哈希表实现,可以是有序符号表、树状符号表、栈结构符号表等)- 在
语义分析
中:符号表所登记的内容将用于语义检查
(如检查一个名字的使用和原先的说明是否一致)和产生中间代码。 - 在
目标代码生成
阶段:当对符号名进行地址分配
时,符号表是地址分配的依据。
- 在
- **注解处理器:**可以读取、修改、添加抽象语法树中的任意元素。如果这些插件在处理注解期间对语法树进行了修改,编译器将回到解析及填充符号表的过程重新处理,直到所有插入式注解处理器都没有再对语法树进行修改为止,每一次循环称为一个Round; 插入式注解处理器的初始化过程是在initProcessAnnotations()方法中完成的,而它的执行过程则是在processAnnotations()方法中完成的,这个方法判断是否还有新的注解处理器需要执行,如果有的话,通过com.sun.tools.javac.processing.JavacProcessingEnvironment类的doProcessing()方法生成一个新的JavaCompiler对象对编译的后续步骤进行处理。
- 语义分析与字节码生成:
检查的内容包括
:变量使用前是否已被声明、变量与赋值之间的数据类型是否能够匹配等。标注检查步骤在Javac源码中的实现类是com.sun.tools.javac.comp.Attr类和com.sun.tools.javac.comp.Check类数据及控制流分析
: 数据及控制流分析是对程序上下文逻辑更进一步的验证,它可以检查:程序局部变量在使用前是否有赋值
、方法的每条路径是否都有返回值
、是否所有的受查异常都被正确处理了
等。- 语法糖(Syntactic Sugar),也称糖衣语法。指在计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。
泛型、变长参数、自动装箱/拆箱等
,虚拟机运行时不支持这些语法,它们在编译阶段还原回简单的基础语法结构. - **字节码生成:**不仅仅是把前面各个步骤所生成的信息(语法树、符号表)转化成字节码写到磁盘中,编译器还进行了少量的代码添加和转换工作。