非机器语言编写的程序是无法直接被机器执行,所以需要一个具有"翻译"功能的程序来将源代码翻译成可执行的语言程序,而Java语言中javac就充当翻译官的角色,将翻译后的语言交给JVM进行处理。
我们初学Java的时候,通常都会接触过这样一个命令:
javac XX.java
当我们执行这个命令后,就会得到一个class文件,这一步其实就是编译,在前面一节我们提及到Java不同于C/C++,它首先需要将Java文件编译成class文件, 然后再由JVM将二进制文件代码转为与机器适配的机器码,而java文件编译成class文件就是由Javac编译器来完成。通常我们也叫Javac编译器为前端编译器,因为一个传统编译器编译结果是由源码到本地机器码的一个过程,而由于Javac编译器只负责源码到字节码这一步,所以叫前端编译器;字节码到本地机器码这一步是由后端运行时编译器来完成的,比如HotSpot VM中的C1、C2编译器。此外对于程序的优化也主要集中在后端运行时编译,这样可以使非Javac编译器产生Class文件(Scala,Groovy等语言的Class文件)也能享受到编译器优化的好处。
这一节主要来看Javac编译器,关于后端运行时编译器大家可以参考下之前写的的文章:Java的即时编译,这篇文章是很早以前看深入理解Java虚拟机书籍时候写的,后面可能会重新再整理一篇。
Javac编译是使用Java语言实现的,是不是很疑惑,用Java语言编写一个javac编译器去编译Java,那Javac的源码又是如何被编译执行的呢(好像鸡生蛋还是蛋生鸡的问题)? 在上一节我们提及到,汇编,C/C++在机器上都是无法直接运行的,需要使用运行编译器进行编译,而如果编译器不是使用机器语言实现肯定无法直接执行的, 所以最开始的编译器应该是使用0、1代码实现的,不用编译就可以运行,但是如是0、1串完整实现一门语言的编译工作可能非常复杂,通常采用的的方案是使用0、1串实现一个简单编译器A,然后利用编译器A写一个稍微复杂一些的编译器B, 最终得到我们所需要的编译器,比如汇编语言的编译器,这个过程叫做"自展",自展的过程是编译器不断扩展完善的过程。
而使用编译器被编译的语言来编写本编译器(比如Javac编译器),这叫做编译器的"自举",但是自举并不是一步完成的,它需要借助于其他语言的编译器,最开始的java编译器使用C语言实现(C写了一个Oak编译器,而C最开始的编译器是汇编实现),而后用这款编译器编译一个Java实现Java编译器, 而后再次用这个Java编译器编写更加优秀的Java编译器,通过不断的自举最终得到我们知道的javac的编译器。编译器自举一般都是编译器开发的一个里程碑事件, 因为自举意味着被编译的语言荣升成自编译语言,而且编译器拥有自举能力对实现语言的语法语义本身没有限制(可参考编译原理一书)。
到这里我们知道javac编译器是如何而来的,那么我们程序中能不能使用javac编译器呢?答案是肯定的。如果你使用Java实现过的动态编译功能,那么对于JavaCompiler接口肯定不陌生,JavaCompiler接口是Java SE6中为我们提供了标准的包来操作Java编译器, 而在JDK6之前,我们如果想操作编译器就需要通过tools.jar中的com.sun.tools.javac包来调用Java编译器,在这个包的根目录提供了主类完成编译的功能,也就是我们是使用javac命令所执行的程序, 但是由于tools.jar不是标准的Java库,使用时需要设置这个jar的路径, 所以在1.6中提供javax.tools包,但其内部核心仍然是使用tools.javac包的api,大家可以研究一下ToolProvider类。
而Javac编译器具体的编译过程可以分为两大步骤(具体可看com.sun.tools.javac.main.JavaCompiler类源码):解析与填充符号表,语义分析及字节码生成。
具体流程:
词法分析器:将源码转换为Token流 将源代码划分成一个个Token(找出java语言中的if,else,for等关键字)
语法分析器:将Token流转化为语法树 将上述的一个个Token组成一句句话(或者说成一句句代码块),检查这一句句话是不是符合Java语言规范(如if后面跟的是不是布尔判断表达式)
语义分析器:将语法树转化为注解语法树 将复杂的语法转化成简单的语法(eg.注解、foreach转化为for循环、去掉永不会用到的代码块)并做一些检查,添加一些代码(默认构造器)
代码生成器:将注解语法树转化为字节码(即将一个数据结构转化成另一个数据结构)
这里对于javac编译器做一个简单叙述,感兴趣可以通过OpenJDK来下载源码,然后自己编译javac的源码, 也可以通过调用jdk的com.sun.tools.javac.main.Main类来手动编译指定的类