目录


4-Java 编程语言编译器组

4.1 简介

该小组由参与设计、实现和维护 Java 编程语言的javac编译器以及相关组件(例如javadoc和 javap )的开发人员组成。

javac编译器读取用Java 编程语言编写的源文件,并将它们编译成类文件。Java 编程语言由​​Java 语言规范(JLS) 定义​​​,类文件由​​Java 虚拟机规范(JVMS) 定义​​。

或者,编译器还可以使用 ​​Pluggable Annotation Processing API ( JSR 269class ) ​​处理在源代码和文件中找到的注释。

编译器是一个命令行工具,但也可以使用 ​​Java Compiler API ​​( JSR 199 ) 调用。

源代码
编译器javac源代码可在 OpenJDK 存储库中获得,网址为​​​http://git.openjdk.java.net/​​。

主线资源位于​​http://git.openjdk.java.net/jdk​​​。
有关 OpenJDK 存储库以及如何克隆它们的一般信息,请参阅The OpenJDK Developer’s Guide。

请注意,javac的本机启动器的代码与所有其他 JDK 工具共享,并在构建期间为 javac定制。

问题
在​​​JDK Bug System​​中跟踪问题。大多数编译器错误都在工具组件javac 子组件中进行跟踪。

4.2 编译器包概述

此表显示了构成编译器的包。除非另有说明,所有包都在 com.sun.tools.javac.*.

[Open JDK-11 源码解析系列]-4-Java 编程语言编译器源码相关笔记_开发语言

官方 API

  • javax.annotation.processing
    用于声明注释处理器和允许注释处理器与注释处理工具环境通信的设施。
  • javax.lang.model 和子包
    用于对 Java 编程语言建模的包的类和层次结构。
  • javax.tools
    为可以从程序调用的工具(例如编译器)提供接口。

支持的 API Supported API

  • com.sun.source 和子包
    编译器语法树的表示。
    -(顶层)
    javac 主程序和命令行入口点为编译器的非常简单的调用提供了支持的 API。

内部 API 和实现类

  • api
    JavaCompiler 和其他 API 在javax.tools.
  • 代码 ode
    表示 Java 程序的语义元素,例如符号、范围和类型,在 javax.lang.model.*.
  • 补偿 omp
    编译器的主要处理阶段,例如归因、流分析、“去除语法糖”和类型擦除。
  • 文件file
    访问本机文件系统,包括 javax.tools.StandardFileManager 的执行。
  • 文件file
    使用 java.nio.file API 访问本机文件系统,包括 JavacPathFileManager。
  • 虚拟机jvm
    用于读写类文件的类,以及编译器的代码生成阶段。
  • 主要的 file
    任何编译的主要驱动程序代码。它通过编译的各个步骤提供选项解码和序列。
  • 模型 model
    的附加实现类javax.lang.model.*。
  • 解析器 parser
    阅读 Java 源代码以生成语法树。
  • 加工 processing

.中定义的注释处理 API 的实现类 javax.annotation.processing.*。

  • 资源 resources
    本地化消息和版本信息的资源文件。
  • 树 tree
    编译器语法树的表示和实用程序类,在com.sun.source.*.
  • 实用程序 util
    基本实用程序类,包括对报告诊断的支持。

4.3 编译器概述

将一组源文件编译成对应的一组类文件的过程并不是一个简单的过程,一般可以分为三个阶段。源文件的不同部分可以“根据需要”以不同的速率通过该过程。
[Open JDK-11 源码解析系列]-4-Java 编程语言编译器源码相关笔记_编译器_02
这个过程由JavaCompiler 类处理。

  • 命令行上指定的所有源文件都被读取,解析成语法树,然后所有外部可见的定义都被输入到编译器的符号表中。
  • 调用所有适当的注释处理器。如果任何注释处理器生成任何新的源文件或类文件,则重新启动编译,直到没有新文件被创建。
  • 最后,解析器创建的语法树被分析并翻译成类文件。在分析过程中,可能会发现对其他类的引用。编译器将检查这些类的源代码和类路径;如果在源路径上找到它们,这些文件也将被编译,尽管它们不会受到注释处理。

解析并输入

源文件针对 Unicode 转义进行处理,并由Scanner.

令牌流由Parser, 读取以创建语法树,使用TreeMaker. 语法树是从JCTree实现 com.sun.source.Tree的子类型及其子类型构建的。

每棵树都被传递给Enter,它将符号中遇到的所有定义的符号输入到符号中。这必须在分析可能引用这些符号的树之前完成。此阶段的输出是一个待办事项列表,其中包含需要分析并生成类文件的树。

Enter由阶段组成;类通过队列从一个阶段迁移到下一个阶段。
[Open JDK-11 源码解析系列]-4-Java 编程语言编译器源码相关笔记_java_03

  • 在第一阶段,所有类符号都被输入到它们的封闭范围内,对于属于其他类成员的类,递归地沿着树递减。类符号被赋予一个 MemberEnter对象作为完成者。
    此外,如果package-info.java找到包含包注释的任何文件,则该文件的顶级树节点也将放在To Do列表中。
  • 在第二阶段,使用 MemberEnter.complete() 完成课程。完成可能会按需发生,但任何未以这种方式完成的类最终将通过处理未完成的队列来完成。完成需要
  • (1) 确定一个类的参数、超类型和接口。
  • (2) 将类中定义的所有符号都输入其范围,但已在阶段 1 中输入的类符号除外。

(2) 取决于 (1) 已完成一个类及其所有超类和封闭类。这就是为什么在执行 (1) 之后,我们将类放入半完成队列中。只有当我们对一个类及其所有超类和封闭类执行了 (1) 时,我们才继续进行 (2)。
输入所有符号后,将分析和验证在这些符号上遇到的任何注释。

第一阶段组织为对所有已编译语法树的扫描,而第二阶段是按需进行的。当第一次访问类的内容时输入类的成员。这是通过在已编译类的类符号中安装完成器对象来实现的,这些类为相应的类树调用 MemberEnter 阶段。

注释处理

这部分过程由 JavacProcessingEnvironment.

从概念上讲,注释处理是编译前的预备步骤。这个初步步骤由一系列轮次组成,每轮都解析和输入源文件,然后确定和调用任何适当的注释处理器。在初始轮次之后,如果任何被调用的注释处理器生成任何需要成为最终编译一部分的新源文件或类文件,则将执行后续轮次。最后,当所有必要的回合都完成后,执行实际的编译。

[Open JDK-11 源码解析系列]-4-Java 编程语言编译器源码相关笔记_java_04

在实践中,在要编译的文件被解析并且它们包含的声明已经被确定之前,可能不知道调用任何注释处理器的需要。因此,为了避免在不执行注释处理的情况下不必要地解析和输入源文件, JavacProcessingEnvironment执行与概念模型有些“异相”,同时仍然满足注释处理作为一个整体发生在实际编译之前的概念要求.

[Open JDK-11 源码解析系列]-4-Java 编程语言编译器源码相关笔记_编译器_05
JavacProcessingEnvironment在要编译的文件已经被解析和输入后调用。它确定是否需要为正在编译的任何文件加载和调用任何注释处理器。通常,如果在整个编译过程中出现任何错误,该过程会在下一个方便的点停止。但是,如果在该阶段检测到任何丢失的符号,则会出现异常Enter ,因为调用注释处理器可能会生成这些符号的定义。

如果要运行注释处理器,它们将在单独的类加载器中加载和运行。

当注释处理器已经运行时, JavacProcessingEnvironment确定是否需要另一轮注释处理。如果是这样,它会创建一个新 JavaCompiler对象,读取任何新生成的需要解析的源文件,并重用任何以前解析过的语法树。所有这些树都被输入到这个新编译器实例的符号表中,并根据需要调用注释处理器。重复此步骤,直到不再需要多轮注释处理。

最后,JavacProcessingEnvironment返回 JavaCompiler要用于其余编译的对象。JavacProcessingEnvironment这要么是用于解析和输入初始文件集的原始实例,要么是用于开始最后一轮编译的最新实例。

分析和生成

一旦命令行上指定的所有文件都被解析并输入到编译器的符号表中,并且在进行了任何注释处理之后,JavaCompiler就可以继续分析被解析的语法树,以生成相应的类文件。

在分析树时,可能会找到对成功编译所需但未明确指定编译的类的引用。根据编译选项,将在源路径和类路径中搜索此类定义。如果在类文件中找到定义,则读取类文件以确定该类中的定义;如果在源文件中找到定义,源文件将被自动解析、输入并放入待办事项列表中。这是通过注册JavaCompiler为Attr.SourceCompleter.

分析树和生成类文件的工作由一系列访问者执行,这些访问者处理编译器的To Do列表中的条目。没有要求这些访问者应该逐步应用于所有源文件,实际上,内存问题会使这非常不受欢迎。唯一的要求是待办事项列表中的每个条目最终都应该由这些访问者中的每个访问者处理,除非编译由于错误而提前终止。

  • 属性
    顶级类是“归属的”,使用Attr,这意味着语法树中的名称、表达式和其他元素被解析并与相应的类型和符号相关联。许多语义错误可以在这里检测到,要么通过Attr,要么通过Check。
  • 流动
    如果到目前为止没有错误,将对类进行流分析,使用Flow. 流分析用于检查变量的明确分配和无法访问的语句,这可能会导致额外的错误。
  • 转换类型
    涉及泛型类型的代码被转换为没有泛型类型的代码,使用TransTypes.
  • 降低
    使用 处理“语法糖” Lower,它重写语法树以通过替换等效的更简单的树来消除特定类型的子树。这负责嵌套和内部类、类文字、断言、foreach 循环等。对于每个已处理的类,Lower返回已翻译类及其所有已翻译嵌套类和内部类的树列表。

虽然Lower通常处理顶级类,但它也会处理 package-info.java. 对于这样的树,Lower 将创建一个合成类来包含包的任何注释。

  • Gen
    方法代码由 生成Gen,它创建Code包含 JVM 执行方法所需的字节码的属性。如果该步骤成功,则该类由 写出ClassWriter。

一旦一个类被写成一个类文件,它的大部分语法树和生成的字节码将不再需要。为了节省内存,对树和符号的这些部分的引用将被清空,以允许垃圾收集器回收内存。

相关工具

  • asmtools – 汇编器工具​​github​
  • btrace – 动态 Java 字节码跟踪工具​​github​
  • defpath – 用于编辑默认路径设置的 Mercurial 扩展
  • doccheck – 检查 JDK 文档中的 HTML 文件的实用程序
  • friday-stats – 用于分析 OpenJDK 的小型实用程序
  • jcheck – Mercurial 变更集验证器
  • jcov – 代码覆盖工具
  • jcstress – Java 并发压力测试
  • jemmy – UI 测试自动化库
  • jextract - 本机库绑定提取工具
  • jmh – Java Microbenchmark Harness
  • jmh-jdk-microbenchmarks – JMH JDK 微基准测试
  • jol – Java 对象布局
  • jtharness – JavaTest Harness
  • jtreg – OpenJDK 平台的回归测试工具:jtreg
  • sigtest – 签名检查工具
  • trees - 用于管理多个存储库的 Mercurial 扩展
  • webrev – 为代码审查生成基于 Web 的差异的工具