编译期优化

本文参考自来自周志明《深入理解Java虚拟机(第2版)》,拓展内容建议读者可以阅读下这本书。

文字版如下:

编译期优化

javac的编译过程

解析和填充符号表

  • 解析 Parse

    • 词法分析

      • 源代码字符流 -> 标记Token
      • 源代码字符是程序编写的最小单位,Token是编译过程的最小元素不可再分
    • 语法分析

      • Token序列 -> 抽象语法树AST
      • AST描述代码语法结构的树状结构,每个节点表示一个语法结构(包、类型、修饰符、运算符、接口、返回值、注释等)
  • 填充符号表 Enter

    • 符号表 -> 待处理列表To Do list
    • 符号表symbol table是由一组符号地址和符号信息构成的表
    • 包、类、方法、字段都可以抽象成一个符号(symbol),不同种类的符号之间可以有包含嵌套关系,这一阶段的任务就是识别出各类符号,并对不同种类的符号按照包含嵌套关系进行归类,并挂接到AST对应的结点上。

插入式注解处理器的注解处理

  • 使用JDK1.6后提供的插入式注解标准API来开发,这些API用于读取、修改、添加语法树的元素,运行时使用-processor参数来参与javac的编译
  • 插入式注解处理器起编译期插件作用
  • 在编译期间插入式注解处理器对注解进行处理,可以读取、修改、添加语法树的元素
  • 如果处理过程中修改了语法树将会触发编译期回到填充符号表阶段重新处理一轮(Round),直到语法树不再变化
  • 实例:lombok,在设置了相关注解后lombok会在编译期生成源代码中没有的方法等

分析与字节码生成

  • 语义分析

    • 标注检查

      • 变量使用前是否已经被声明
      • 变量与赋值之间的数据类型是否能够匹配
      • 常量折叠
    • 数据及控制流检查

      • 程序局部变量在使用前是否有赋值
      • 方法的每条路径是否都有返回值
      • 是否所有的受检异常都被正确处理
      • final参数、final局部变量是否被修改

        • 局部变量是否用final修饰最终在class文件中是无差别的,因为局部变量在class中是没有访问标志的,这个final不变性只在编译期检查时保证
    • 解语法糖

      • 泛型:编译后发生类型擦除,但是编译信息会保留Signature
      • 变长参数:编译后变成数组类型的参数
      • 自动装箱拆箱:编译后装箱通过valueOf()变成了对象,拆箱通过xxxValue()变成了原始类型值
      • 遍历循环:编译后变成了迭代器遍历
      • 条件编译:编译后将常量不可达条件分支直接消除掉
      • 内部类
      • 枚举类
      • 断言
      • switch处理枚举和字符串
      • try定义和关闭资源
  • 字节码生成

    • 处理完的语法树和符号表 -> 字节码
    • 少量的代码添加和转换工作

      • 添加<init>()和<clinit>()方法

        • <init>()收敛的操作

          • 调用父类的<init>()
          • 执行{}语句块
          • 初始化实例变量
        • <init>()是字节码级别的方法,对使用者是透明的,与每个<init>()对应的构造方法是源码级别的方法,是真正被使用者开放的。<init>()和构造方法一一对应,是同一概念在两个级别的含义
        • <clinit>()收敛的操作

          • (JVM自动保证执行父类的<clinit>())
          • 执行static{}语句块
          • 初始化类变量
      • 字符串加操作替换为StringBuffer或StringBuilder的append()操作

JinhaoPlus
1.5k 声望92 粉丝

扎瓦程序员