JVM 与编译器

Java 代码编译机制

JVM 规范中定义了 class 文件的格式,但并未定义 Java 源码如何被编译为 class 文件,各厂商在实现 JDK 时通常会将符合 Java 语言规范的源码编译为 class 文件的编译器,例如在 Sun JDK 中就是 javac,javac 将 Java 源码编译为 class 文件的步骤如下图所示:
Java代码编译机制

分析和输入到符号表(Parse and Enter)

Parse 过程所做的为此法和语法分析。此法分析(com.sun.tools.javac.parser.Scanner)要完成的是将代码字符串转变为 token 序列(例如 Token.EQ(name:=));语法分析(com.sun.tools.javac.parser.Parser)要完成的是根据语法由 token 序列生成抽象语法树。
Enter(com.sun.tools.javac.comp.Enter)过程为将符号输入到符号表,通常包括确定类的超类型和接口、根据需要添加默认构造器、将类中出现的符号输入类自身的符号表中等。

注解处理(Annotation Processing)

该步骤主要用于处理用户自定义的 annotation,可能带来的好处是基于 annotation 来生成附加的代码或进行一些特殊的检查,从而节省一些共用的代码的编写,例如当采用 Lombok 时,可编写如下代码:

1
2
3
public class User {
private @Getter String username;
}

编译时引入 Lombok 对 User.java 进行编译后,再通过 javap 查看 class 文件可看到自动生成了 public String getUsername()方法。
此功能基于 JSR 269,在 Sun JDK 6 中提供了支持,在 Annotation Processing 进行后,再次进入 Parse and Enter 步骤。

语义分析和生成 class 文件(Analyse and Generate)

Analyse 步骤基于抽象语法树进行一系列的语义分析,包括:

  • 将语法树中的名字、表达式等元素与变量、方法、类型等联系到一起;
  • 检查变量使用前是否已声明;
  • 推导泛型方法的类型参数;
  • 检查类型匹配性;
  • 进行常量折叠;
  • 检查所有语句都可到达;
  • 检查所有 checked exception 都被捕获或抛出;
  • 检查变量的确定性赋值(例如有返回值的方法必须确定有返回值);
  • 检查变量的确定性不重复赋值(例如声明为 final 的变量等);
  • 解除语法糖(消除 if(false) { … }形式的无用代码;将泛型 Java 转为普通 Java;将含有语法糖的语法树改为含有简单语言结构的语法树,例如 foreach 循环、自动装箱/拆箱等);
  • 等…

在完成了语义分析后,开始生成 class 文件(com.sun.tools.javac.jvm.Gen),生成的步骤为:

  • 首先将实例成员初始化器收集到构造器中,将静态成员初始化器收集为();
  • 接着将抽象语法树生成字节码,采用的方法为后序遍历语法树,并进行最后的少量代码转换(例如 String 相加转变为 StringBuilder 操作);
  • 最后从符号表生成 class 文件。

参考

  1. 深入浅出 JIT 编译器