深入理解Java虚拟机::前段编译与优化

前段编译与优化

  • 前端编译器:把 *.java 文件转变成 *.class 文件
  • 即时编译器:运行期把字节码转变成本地机器码
  • 静态的提前编译器:直接把程序编译成与目标机器指令集相关的二进制代码
  • Javac 编译器
    • Java 语言实现
    • 编译过程
      • 准备过程:初始化插入式注解处理器。
        • 提前至编译期对代码中的特定注解进行处理,从而影响到前端编译器的工作过程
        • 我们可以把插入式注解处理器看作是一组编译器的插件,当这些插件工作时,允许读取、修改、添加抽象语法树中的任意元素。如果这些插件在处理注解期间对语法树进行过修改,编译器将回到解析及填充符号表的过程重新处理,直到所有插入式注解处理器都没有再对语法树进行修改为止
      • 解析与填充符号表
        • 词法、语法分析。将源代码的字符流转变为标记集合,构造出抽象语法树
          • 词法分析是将源代码的字符流转变为标记(Token)集合的过程,单个字符是程序编写时的最小元素
          • 词法分析过程由 com.sun.tools.javac.parser.Scanner 类来实现。
        • 填充符号表。产生符号地址和符号信息
          • 符号表(Symbol Table)是由一组符号地址和符号信息构成的数据结构
          • 符号表中所登记的信息在编译的不同阶段都要被用到。
          • 在 Javac 源代码中,填充符号表的过程由 com.sun.tools.javac.comp.Enter 类实现
          • 该过程的产出物是一个待处理列表,其中包含了每一个编译单元的抽象语法树的顶级节点,
      • 分析与字节码生成过程
        • 标注检查。对语法的静态信息进行检查。
          • 标注检查步骤要检查的内容包括诸如变量使用前是否已被声明、变量与赋值之间的数据类型是否能够匹配
          • 顺便进行一个称为常量折叠的代码优化
        • 数据流及控制流分析。对程序动态运行过程进行检查。
          • 数据流分析和控制流分析是对程序上下文逻辑更进一步的验证,它可以检查出诸如程序局部变量在使用前是否有赋值、方法的每条路径是否都有返回值、是否所有的受查异常都被正确处理了等问题。
        • 解语法糖。将简化代码编写的语法糖还原为原有的形式。
          • 计算机语言中添加的某种语法,这种语法对语言的编译结果和功能并没有实际影响,但是却能更方便程序员使用该语言
          • Java 中最常见的语法糖包括了前面提到过的泛型、变长参数、自动装箱拆箱,等等
        • 字节码生成。将前面各个步骤所生成的信息转化成字节码。
          • 在 Javac 源码里面由 com.sun.tools.javac.jvm.Gen 类来完成。字节码生成阶段不仅仅是把前面各个步骤所生成的信息(语法树、符号表)转化成字节码指令写到磁盘中,编译器还进行了少量的代码添加和转换工作。
          • 完成了对语法树的遍历和调整之后,就会把填充了所有所需信息的符号表交到 com.sun.tools.javac.jvm.ClassWriter 类手上,由这个类的 writeClass()方法输出字节码,生成最终的 Class 文件,到此,整个编译过程宣告结束。
  • 语法糖
    • 泛型
      • 泛型的本质是参数化类型(Parameterized Type)或者参数化多态(Parametric Polymorphism)的应用,即可以将操作的数据类型指定为方法签名中的一种特殊参数,这种参数类型能够用在类、接口 和方法的创建中,分别构成泛型类、泛型接口和泛型方法。泛型让程序员能够针对泛化的数据类型编写相同的算法,这极大地增强了编程语言的类型系统及抽象能力。
      • 类型擦除式泛型
      • Java 语言中的泛型只在程序源码中存在,在编译后的字节码文件中,全部泛型都被替换为原来的裸类型了,并且在相应的地方插入了强制 转型代码,因此对于运行期的 Java 语言来说,ArrayList与 ArrayList其实是同一个类型
      • 擦除式泛型的实现几乎只需要在 Javac 编译器上做出改进即可,不需要改动字节码、不需要改动 Java 虚拟机,也保证了以前没有使用泛型的库可以直接运行在 Java 5.0 之上。
      • 擦除法实现泛型直接无法支持原生类型的泛型
      • 运行期无法取到泛型类型信息,会让一些代码变得相当啰嗦,需要传 class 类型才能确定
      • 类型擦除导致无法重载
    • 自动装箱、拆箱与遍历循环
      • 自动装箱、拆箱在编译之后被转化成了对应的包装和还原方法
      • 遍历循环则是把代码还原成了迭代器的实现
    • 条件编译
      • 我不知道作者怎么理解条件编译的,但是他举的内容是编译优化里面的常见的死码消除