非机器语言编写的程序是无法直接被机器执行,所以需要一个具有"翻译"功能的程序来将源代码翻译成可执行的语言程序,而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编译器),这叫做编译器的"自举",但是自举并不是一步完成的,它需要借