Java 编译器的内部架构
Internal Architecture of Java Compiler
我从事 Java
已有 8 年多了。
上周,在我公司的一个小型会议上,我的一位同事问我 Java Compiler
究竟是如何工作的?我没有回答。
我试着解释一下,比如 Java 编译器一个一个地获取语句并将它们转换为字节码,这些字节码不是针对任何 OS
而是针对 JVM
.
即使是我,也没有人对这个答案感到满意。
现在的主要问题是 java 编译器究竟是如何工作的。即在编译 Java
文件的情况下,编译器将完成多少个步骤或阶段。
Java's compiler
架构究竟是什么?
如果同一个 .java 文件中有多个 Java classes
怎么办。那么要编译多少类
如果有导入指向 un-compiled Java 类 怎么办?那么un-compiled类被编译还是被忽略?
我用谷歌搜索了半天多,所有答案都和我给同事的一样。
但我终于找到了一些有用的教程here。
但是教程也涵盖的内容不多in-depth,我无法想象那个教程。
我仍然不满意并渴望从你那里学到更多关于这方面的东西。
所以如果有人比我和上面的博客更了解一些东西,通过使用它我可以想象 Java Compiler
的内部架构到底是什么,请解释一下。
一些基本步骤:
- parse: 读取一组 *.java 源文件并映射生成的令牌
序列到 AST(抽象语法树)-Nodes.
- enter:将定义的符号输入符号 table。
- 处理注释:如果需要,处理在中找到的注释
指定的编译单元。
- attribute:语法树的属性。此步骤包括名称
分辨率、类型检查和常量折叠。
- flow:对上一步的树进行数据流分析。
这包括检查分配和可达性。
- desugar:重写 AST 并转换掉一些语法糖。
- 生成:生成源文件或 Class 文件。
更多详情:
- Lex - 将源文件分解成单独的单词或标记。
- Parse - 分析程序的短语结构。
- Semantic Actions - 为每个短语建立一个抽象语法树。
- 语义分析 - 确定每个短语的含义,将变量的使用与其定义相关联,检查表达式类型,请求翻译每个短语。
- 框架布局 - 以机器相关的方式将变量、函数参数等放入激活记录(堆栈框架)中。
- 翻译 - 产生中间表示树(IR树),一种表示法
与任何特定的源语言或目标机器体系结构无关。
- Canonicalize - 提升表达式的副作用,并清理条件分支,以方便下一阶段。
- 指令选择 - 将 IR 树节点分组为与目标机器指令的操作相对应的块。
控制流分析 - 将指令序列分析成控制流图,显示程序可能的所有可能控制流
执行时跟随。
数据流分析 - 通过程序变量收集有关信息流的信息;例如,活跃度分析计算每个程序变量保持仍然需要的值(是活跃的)的地方。
Register Allocation - 选择一个寄存器来保存程序使用的每个变量和临时值;变量不同时存在
可以共享同一个寄存器。
代码发射 - 将每条机器指令中的临时名称替换为
机器寄存器。
有一本好书:
Modern Compiler Implementation in Java
你可能想看看里面的 javac 代码:
Don't Panic! To help newcomers to javac navigate their way around the code base
编译器有不同的步骤,但最重要的是:
词法分析
第一步是词法分析。基本上,此步骤从 java 代码中提取标记(关键字、运算符、分隔符、注释、变量名...)
语法分析(解析器)
第二步是语法分析。词法分析将标记作为输入,并组合形成表达式和指令。
优化并转换为字节码
最后一个宏步骤是将上一步转换为字节码。这里的代码可以修改为和原来的代码等价但是效率更高
注:这个过程不仅仅与java有关,而是所有编译器通用的。还有不生成中间字节代码而是生成机器代码的编译器(如 C 或 C++ 的编译器)。
一般有创建词法分析器和语法分析器的工具,因为这个步骤在不同语言之间有很多共同点。
开源词法分析器是 flex
一个有用的句法分析器是 yacc
两者都适用于创建编译器最常用的语言 C 和 C++(java 和其他语言),但其他编程语言也有类似的替代方案(创建编译器 in 另一种语言,而不是 for 另一种语言)。基本上编写编译器的语言与编译器编译的语言无关。
我从事 Java
已有 8 年多了。
上周,在我公司的一个小型会议上,我的一位同事问我 Java Compiler
究竟是如何工作的?我没有回答。
我试着解释一下,比如 Java 编译器一个一个地获取语句并将它们转换为字节码,这些字节码不是针对任何 OS
而是针对 JVM
.
即使是我,也没有人对这个答案感到满意。
现在的主要问题是 java 编译器究竟是如何工作的。即在编译 Java
文件的情况下,编译器将完成多少个步骤或阶段。
Java's compiler
架构究竟是什么?
如果同一个 .java 文件中有多个 Java classes
怎么办。那么要编译多少类
如果有导入指向 un-compiled Java 类 怎么办?那么un-compiled类被编译还是被忽略?
我用谷歌搜索了半天多,所有答案都和我给同事的一样。
但我终于找到了一些有用的教程here。
但是教程也涵盖的内容不多in-depth,我无法想象那个教程。
我仍然不满意并渴望从你那里学到更多关于这方面的东西。
所以如果有人比我和上面的博客更了解一些东西,通过使用它我可以想象 Java Compiler
的内部架构到底是什么,请解释一下。
一些基本步骤:
- parse: 读取一组 *.java 源文件并映射生成的令牌 序列到 AST(抽象语法树)-Nodes.
- enter:将定义的符号输入符号 table。
- 处理注释:如果需要,处理在中找到的注释 指定的编译单元。
- attribute:语法树的属性。此步骤包括名称 分辨率、类型检查和常量折叠。
- flow:对上一步的树进行数据流分析。 这包括检查分配和可达性。
- desugar:重写 AST 并转换掉一些语法糖。
- 生成:生成源文件或 Class 文件。
更多详情:
- Lex - 将源文件分解成单独的单词或标记。
- Parse - 分析程序的短语结构。
- Semantic Actions - 为每个短语建立一个抽象语法树。
- 语义分析 - 确定每个短语的含义,将变量的使用与其定义相关联,检查表达式类型,请求翻译每个短语。
- 框架布局 - 以机器相关的方式将变量、函数参数等放入激活记录(堆栈框架)中。
- 翻译 - 产生中间表示树(IR树),一种表示法 与任何特定的源语言或目标机器体系结构无关。
- Canonicalize - 提升表达式的副作用,并清理条件分支,以方便下一阶段。
- 指令选择 - 将 IR 树节点分组为与目标机器指令的操作相对应的块。
控制流分析 - 将指令序列分析成控制流图,显示程序可能的所有可能控制流 执行时跟随。
数据流分析 - 通过程序变量收集有关信息流的信息;例如,活跃度分析计算每个程序变量保持仍然需要的值(是活跃的)的地方。
Register Allocation - 选择一个寄存器来保存程序使用的每个变量和临时值;变量不同时存在 可以共享同一个寄存器。
代码发射 - 将每条机器指令中的临时名称替换为 机器寄存器。
有一本好书:
Modern Compiler Implementation in Java
你可能想看看里面的 javac 代码:
Don't Panic! To help newcomers to javac navigate their way around the code base
编译器有不同的步骤,但最重要的是:
词法分析 第一步是词法分析。基本上,此步骤从 java 代码中提取标记(关键字、运算符、分隔符、注释、变量名...)
语法分析(解析器) 第二步是语法分析。词法分析将标记作为输入,并组合形成表达式和指令。
优化并转换为字节码 最后一个宏步骤是将上一步转换为字节码。这里的代码可以修改为和原来的代码等价但是效率更高
注:这个过程不仅仅与java有关,而是所有编译器通用的。还有不生成中间字节代码而是生成机器代码的编译器(如 C 或 C++ 的编译器)。
一般有创建词法分析器和语法分析器的工具,因为这个步骤在不同语言之间有很多共同点。
开源词法分析器是 flex 一个有用的句法分析器是 yacc
两者都适用于创建编译器最常用的语言 C 和 C++(java 和其他语言),但其他编程语言也有类似的替代方案(创建编译器 in 另一种语言,而不是 for 另一种语言)。基本上编写编译器的语言与编译器编译的语言无关。