Javac编译器工作原理(2)Java语言的编译过程
明白了高级语言到低级语言的编译原理,我们来了解一下Javac编译器是怎么把Java语言,编译成JVM字节码
首先我们来了解一下,Javac编译器
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;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
使用javac编译器进行编译
javac SimpleTest.java
- 1
也就是编译失败了,javac编译器则会给你这样的错误提示
javac编译过程主要包括以下四步:
词法分析->语法分析->语义分析->字节码生成
1. 词法分析
词法分析将java源代码一个字节一个字节的读进来,根据关键字,辨认出符合规范的Token流。
这样说可能有些抽象,其实就是把一段java代码分解,成为一个个单独的词。
public class SimpleTest{
// 域,方法代码块等等
}
- 1
- 2
- 3
会分解为不同的Token,第一个就是关键字public,对应的Token类为:Token.PUBLIC。同理class关键字对应:Token.ClASS
根据这些Token流,编译器完成了理解Java语言的第一步,就像你听到一句话,我要吃饭,大脑中第一步是把这句话分解为:我,要,吃饭。
2.语法分析
语法分析器会读取Token流,然后生成一棵语法树,类似下图
而Javac此时生成的是一棵简单的Java语法树,树的节点是全部是com.sun.tools.javac.tree.JCTree的子类
下图就是一棵图像化的java简单语法树
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);
- 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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
其他代码可以先不用理解,但是看到invokevirtual #4 // Method java/lang/Integer.intValue,说明编译器自动添加代码,调用了Integer.intValue()方法,完成变量自动拆箱,该方法的返回值是基本类型int。
也相当于java代码变为:
Integer temp = new Integer(1);
int i = temp.intValue();
- 1
- 2
当然javac不是使用这种简陋的方法,而是使用了更高效的字节码代替。
4.生成字节码
经过语法分析以后,生成的java简单语法树会被编译器优化为完善的java语法树
这时Javac编译器调用 com.sun.tools.javac.jvm.Gen类遍历语法树
遍历并生成字节码类似下图
图片来自RednaxelaFX大神的博客文章
遍历语法树后,获得JVM字节码,这时字节码就可以交给JVM执行了
这里要说明一点,编译得到后缀为class文件中,字节码不止包括方法体中的各种指令,还有诸如常量池,标志位,魔数等等二进制数据
想了解JVM字节码的可以参考下面文章:
如何理解ByteCode、IL、汇编等底层语言与上层语言的对应关系? - RednaxelaFX的回答 - 知乎
https://www.zhihu.com/question/27831730/answer/38266643
参考资料:
《深入分析Java Web技术内幕》–许令波
《深入理解Java虚拟机》–周志明
http://rednaxelafx.iteye.com/blog/492667 – 强烈推荐阅读R大的博客文章
https://www.cnblogs.com/java-zhao/p/5194064.html