名师工作室网站建设,巨腾网站建设,wordpress配置搜索引擎优化,用mockplus做网站原型Java 的编译过程
前端编译: 编译器的前端#xff0c;将 Java 文件转变成 Class 文件的过程#xff1b;如 JDK 的 javac、Eclipse JDT 中的增量式编译器 ECJ#xff1b;即使编译: JIT#xff0c;Just In Time Compiler#xff0c;在运行期将字节码转变成本地机器码的过程将 Java 文件转变成 Class 文件的过程如 JDK 的 javac、Eclipse JDT 中的增量式编译器 ECJ即使编译: JITJust In Time Compiler在运行期将字节码转变成本地机器码的过程如 HotSpot VM 的 C1、C2 编译器Graal 编译器提前编译: AOTAhead Of Time Compiler直接静态将程序编译成目标机器指令集的二进制代码的过程如 JDK 的 Jaotc、GNU Compiler for the JavaGCJ、Excelsior JET
即使编译器在运行期的优化支持了程序执行效率的提升而前端编译器在编译期的优化支持了程序员的编码效率和语言使用幸福感的提高 文章目录1. javac 的源码与调试2. 解析与填充符号表3. 注解处理器4. 语义分析与字节码生成1. javac 的源码与调试 JDK 6 以前javac 不属于标准 Java SE API代码独立存放在 tools.jar使用时需要将路径加入 ClassPath JDK 6 开始javac 晋升成标准 Java 类库源码放在 JDK_SRC_HOME/langtools/src/share/classes/com/sun/tools/javac JDK 9 开始整个 JDK 的 Java 类库模块化重构javac 编译器放在 jdk.compiler 模块存放路径为 JDK_SRC_HOME/src/jdk.compiler/share/classes/com/sun/tools/javac OpenJDK 源码 可以直接执行 javac.Main 的 main() 方法来执行编译参数与直接使用 javac 命令一致
从 javac 代码的总体结构看编译过程大致可以分为 1 个准备过程和 3 个处理过程
准备过程: 初始化插入式注解处理器解析与填充符号表过程包括: a. 词法、语法分析将源代码的字符流转变为标记集合构造出抽象语法树 b. 填充符号表产生符号地址和符号信息插入式注解处理器的注解处理过程: 插入式注解处理器的执行阶段影响 javac 的编译行为分析与字节码生成过程包括: a. 标注检查对语法的静态信息进行检查 b. 数据流及控制流分析对程序动态运行过程进行检查 c. 解语法糖将简化代码编写的语法糖还原为原有的形式 d. 字节码生成将前面各个步骤所生成的信息转化成字节码
插入式注解可能会产生新的符号如果有新的符号产生就必须转回解析、填充符号表的过程重新处理新的符号 javac 编译入口代码在 com.sun.tools.javac.main.JavaCompiler 类的 compile() 方法
2. 解析与填充符号表 parseFiles: 1.1词法分析、语法分析enterTrees: 1.2输入到符号表processAnnotations: 2执行注解处理
a. 词法、语法分析
词法分析将源代码的字符流转变成标记Token集合字符是程序编写的最小单元而 Token 是编译的最小单元关键字、变量名、字面量、运算符等都是 Token不可再拆分javac 的词法分析由 com.sun.tools.javac.parser.Scanner 类实现语法分析根据 Token 序列构造抽象语法树Abstract Syntax TreeAST描述一个结构正确的源程序程序语言结构的树形表示树的每一个节点代表着程序的一个语法结构Syntax Construct包、类型、修饰符、运算符、接口、返回值、代码注释等都可以是一种特定的语法结构javac 的语法分析由 com.sun.tools.javac.parser.Parser 类实现抽象语法树以 com.sun.tools.javac.tree.JCTree 类表示
b. 填充符号表
符号表Symbol Table一组符号地址和符号信息构成的数据结构包含每个编译单元的抽象语法树的顶级节点和 package-info.java 的顶级节点类似于 Hash 表的键值对存储结构也可以是有序符号表、树状符号表、栈结构符号表等形式符号表在语义分析阶段用于语义检查和产生中间代码在目标代码生成阶段用于地址分配javac 中填充符号表由 com.sun.tools.javac.comp.Enter 类实现
3. 注解处理器
Java 在 JDK 5 开始支持注解Annotations原只对程序运行期间发挥作用到 JDK 6 时添加了插入式注解处理器的标准 API提前至编译期处理特定注解可以影响前端编译器的工作过程
插入式注解处理器相当于编译器的插件通过这些插件可以读取、修改、添加抽象语法树的任意元素若插件在处理注解期间对抽象语法树进行修改编译器将回退至解析和填充符号表的过程直到插入式注解处理器不再修改抽象语法树每一次循环称为一个轮次Round
编码效率工具 Lombok 通过注解实现自动生成 getter/setter 方法、空置检查、生成受查异常表、生成 equals() 和 hashCode() 等功能都是依赖插入式注解处理器实现的 initProcessAnnotations: 准备过程初始化插入式注解处理器processAnnotations: 完成插入式注解执行处理若有新的注解处理器需要执行则通过 com.sun.tools.javac.processing.JavacProcessingEnvironment 类的 doProcessing() 生成一个新的 JavaCompiler 对象进行后续的编译已处理
4. 语义分析与字节码生成
语义分析经过语法分析得到的抽象语法树可以表示一个结构正确的源程序但无法保证源程序的语义符合逻辑语义分析则是对结构上源程序进行上下文相关性质进行检查类型检测、控制流检查、数据流检查等
int a 1;
boolean b false;
char c 2;
int d a c;
int d b c;
char d a c;所有代码都可以构造正确的抽象语法树但后两句在 Java 语言中是不符合逻辑的语义分析异常与具体的语言和上下文环境相关 attribute: 3.1语义分析的标注检查flow: 3.2语义分析的数据及控制流分析desugar: 3.3解语法糖generate: 3.4. 生成字节码
a. 标注检查
进行如变量使用前是否已被声明、变量与赋值之间的数据类型是否匹配等的检查常量折叠Constant Foldingjavac 对源代码做的极少优化之一
int a 1 2;在抽象语法树仍然存在字面量 1、2 和操作符 但经过代码折叠变量的值会被标记为 3因此在代码里定义 a12 和 a3 相比并不会浪费哪怕一个处理器时钟周期的时间
javac 的标记检查由 com.sun.tools.javac.comp.Attr 类和 com.sun.tools.javac.comp.Check 类实现
b. 数据及控制流分析
对程序上下文逻辑进行进一步验证检查如程序局部变量在使用前是否赋值、方法的每个路径是否都有返回值、是否所有受检异常都被正确处理等与类加载时的数据及控制流分析的目的一直但校验范围不同
public void foo(final int arg){final int var 0;// do something;
}public void foo(int arg){int var 0;// do something;
}两种写法经过 javac 编译所得字节码完全一样可见局部变量是否被 final 修饰对运行期是完全无影响的不可知的变量的不可变仅仅有 javac 编译器在编译期保障的
javac 的数据及控制流分析由 com.sun.tools.javac.comp.Flow 类实现
c. 解语法通
语法糖指计算机语言中的某种语法其对语言的编译结果和功能不会有实际影响JVM 不能支持这些语法这些语法最终会被编译成基本语法结构但却可以更方便编写者实用该语言减少代码量、增加可读性、减少出错几率如泛型C# 的泛型是 CLR 支持的不属于语法糖、变长参数、自动装箱拆箱等解语法糖将语法糖编译成原始基本语法结构
javac 的解语法糖由 com.sun.tools.javac.comp.TransTypes 类和 com.sun.tools.javac.comp.Lower 类实现
d. 字节码生成
把语法树、符号表转发成字节码指令写到磁盘并进行少量代码添加和转换工作
代码添加如在语法树中添加实例构造器 init() 和类构造器 clinit()编译器会把语句块init() 的是{}块clinit() 的是static {} 块、变量初始化实例变量和类变量、调用父类的实例构造器只有 init()clinit() 中无须调用父类的 clinit()但经常会生成调用 java.lang.Object 的 init() 的代码等操作收敛到 init() 和 clinit()并保障一定顺序执行先父类实例构造器、再初始化变量、最后语句块代码转换如将字符串的加操作替换为 StringBuilder 或 StringBuffer 的 append() 操作
javac 的字节码生成由 com.sun.tools.javac.jvm.Gen 类实现将填充了所有信息的符号表输出到 Class 文件由 com.sun.tools.javac.jvm.CLassWriter 类实现 上一篇「JVM 原理使用」在远程服务端动态执行临时代码
PS感谢每一位志同道合者的阅读欢迎关注、评论、赞 参考资料
[1]《深入理解 Java 虚拟机》