Javac编译器工作原理(2)Java语言的编译过程

明白了高级语言到低级语言的编译原理,我们来了解一下Javac编译器是怎么把Java语言,编译成JVM字节码

首先我们来了解一下,Javac编译器

javac(发音为“java-see”)是Oracle Corporation的Java Development Kit(JDK)中包含的主要Java编译器。 Martin Odersky实现了GJ编译器,他的实现成为了javac的基础。
编译器接受符合Java语言规范(JLS)的源代码,并生成符合Java虚拟机规范(JVMS)的Java字节码。javac本身是用Java编写的。 编译器也可以以编程方式调用。

简单来讲,就是Javac编译器将符合规范的java语言代码变为符合规范的JVM字节码

实际上,javac在编译的过程中,会对代码进行检查和优化,比如:添加类默认的构造函数,检查变量类型是否匹配,检查变量使用前是否初始化,检查异常以及捕获语句,解除java语法糖等等

例如有这样一段代码

public class SimpleTest {
    public static void main(String[] args) {
        int i;
        int j = i + 10;
    }
}

使用javac编译器进行编译

javac SimpleTest.java

也就是编译失败了,javac编译器则会给你这样的错误提示

Java编译时候javac javac 编译_java

javac编译过程主要包括以下四步:

词法分析->语法分析->语义分析->字节码生成

Java编译时候javac javac 编译_java_02

1. 词法分析

词法分析将java源代码一个字节一个字节的读进来,根据关键字,辨认出符合规范的Token流。
这样说可能有些抽象,其实就是把一段java代码分解,成为一个个单独的词。

public class SimpleTest{
   // 域,方法代码块等等
}

会分解为不同的Token,第一个就是关键字public,对应的Token类为:Token.PUBLIC。同理class关键字对应:Token.ClASS

根据这些Token流,编译器完成了理解Java语言的第一步,就像你听到一句话,我要吃饭,大脑中第一步是把这句话分解为:我,要,吃饭。

2.语法分析

语法分析器会读取Token流,然后生成一棵语法树,类似下图

Java编译时候javac javac 编译_编译器_03

而Javac此时生成的是一棵简单的Java语法树,树的节点是全部是com.sun.tools.javac.tree.JCTree的子类
下图就是一棵图像化的java简单语法树

Java编译时候javac javac 编译_jdk_04

3.语义分析

这个阶段就是编译器对代码进行各种检查和优化,而实际操作的对象就是生成java简单语法树的节点。

这个阶段主要使用到类都在com.sun.tools.javac.comp包

javac编译器首先对代码进行一些检查,保证代码符合java语言规范,比如:

com.sun.tools.javac.comp.Check类会检查简单语法树中,变量类型是否正确,方法返回类型是否与接收的引用值是否匹配等等。
com.sun.tools.javac.Resolve类会检查变量、方法或类的访问是否合法,变量是否是静态变量,变量是否初始化等等。

而javac编译器同时对代码进行一些简单地优化,比如:

com.sun.tools.comp.Flow类会去掉无用代码,比如永假的if语句,同事完成变量的自动转换,主要是自动装箱和拆箱等
com.sun.tools.comp.Flow类还会去除java语法糖,比如foreach转化为for循环等

例如有Java代码:

int i = new Integer(1);

javac编译后生产如下字节码(经过反编译得到的):

0: new           #2                  // class java/lang/Integer
     3: dup
     4: iconst_1
     5: invokespecial #3                  // Method java/lang/Integer."<init>":(I)V
     8: invokevirtual #4                  // Method java/lang/Integer.intValue:()I
    11: istore_1
    12: return

其他代码可以先不用理解,但是看到invokevirtual #4 // Method java/lang/Integer.intValue,说明编译器自动添加代码,调用了Integer.intValue()方法,完成变量自动拆箱,该方法的返回值是基本类型int。

也相当于java代码变为:

Integer temp = new Integer(1);
    int i = temp.intValue();

当然javac不是使用这种简陋的方法,而是使用了更高效的字节码代替。

4.生成字节码

经过语法分析以后,生成的java简单语法树会被编译器优化为完善的java语法树

这时Javac编译器调用 com.sun.tools.javac.jvm.Gen类遍历语法树

遍历并生成字节码类似下图

Java编译时候javac javac 编译_Java编译时候javac_05


遍历语法树后,获得JVM字节码,这时字节码就可以交给JVM执行了

这里要说明一点,编译得到后缀为class文件中,字节码不止包括方法体中的各种指令,还有诸如常量池,标志位,魔数等等二进制数据