深入理解java虚拟机(四)

javac 编译过程

Posted by Kinsomy on May 8, 2019

1. Javac编译过程

Javac是编译器的前端,将java文件转变成class字节码文件。

Javac编译过程大致分三个过程:

  • 解析与填充符号表过程
  • 插入式注解处理器的注解处理过程
  • 分析与字节码生成过程



1.1 解析与填充符号表

解析步骤包括词法分析和语法分析两个过程。

词法分析将源代码转变为标记,将代码拆分成最小元素,可以被标记的最小元素包括关键字、变量名、字面量、运算符等。例如int a = b + c就被拆分成了”int“、”a“、”=“、”b“、”+“、”c“六个标记。

语法分析是根据词法分析解析出来的标记集合构造抽象语法树的过程,抽象语法树用来描述源代码语法结构。

填充符号表在词法、语法分析完成之后进行,符号表里存储的是符号地址和符号信息的映射,表里的内容用于语义检查和产生中间代码。

1.2 注解处理

注解处理是针对jdk1.6提供的插入式注解处理器标准api而言的,注解处理器可以用来读取、修改、添加抽象语法树中的任意元素,编译器在注解处理过程中处理了抽象语法树的修改,那么就要重新进行解析和填充符号表操作,知道在注解处理阶段没有再需要处理的注解为止。

1.3 语义分析和字节码生成

在上面的解析步骤生成了抽象语法树,语义分析会检查语法树是否符合java语言程序逻辑。

语义分析分为标注检查和数据及控制流分析两块。

1)标注检查是对变量使用前是否已经声明、变量类型正确性、类型匹配等。

常量折叠:
源代码中的`int a = 1 + 2`在语法树中会被`常量折叠`成字面量3,在运行期间就可以直接使用`a=3`,所以编写代码时`int a = 1 + 2`的写法和`int a = 3`在运行时的运算消耗没有差别。

2)数据及控制流分析是对代码根据上下文做进一步的检查,例如检查局部变量是否在使用前赋值、方法的每条路径是否有返回、受检异常是否被正确处理等。

字节码生成是Javac编译的最后一步,它会将之前生成的语法树和符号表转化成字节码文件,同时添加一些代码到语法树,并做一定的代码转换优化工作。

实例构造器<init>()方法和类构造器<clinit>()在字节码生成阶段被加入到语法树中

经过上述的所有步骤之后,就会得到class文件,编译过程结束。

2. Java语法糖

2.1 泛型和类型擦除

java的泛型是一个语法糖,List和List两个不同的list在运行期实际是同一个类,这个实现叫类型擦除。

针对java的类型擦除的实现,来看一组代码:

public void method(List<Integer> list) {
    System.out.println("medthod(List<Integer> list)");
}

public void method(List<String> list) {
    System.out.println("method(List<String> list)");
}

在一个类中写了这样一组方法之后IDE会报一个error:

'method(List<Integer>)' clashes with 'method(List<String>)'; both methods have same erasure

这两个方法是不能重载的,虽然他们的参数泛型不同,但是经过编译器类型擦出之后,参数都会变成List<E>,会导致函数签名一模一样。

参考资料

  • 深入理解Java虚拟机:JVM高级特性与最佳实践