了解了从中级语言到低级语言的编译原理之后,我们来了解一下Javac编译器是如何将Java语言编译成JVM字节码的
首先我们看一下Javac编译器
Javac 编译器 - 维基百科
javac(发音为“java-see”)是 Oracle 公司 Java 开发工具包 (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编译器会给你这样的错误信息
javac编译过程主要包括以下四个步骤:
词法分析 -> 句子分析 -> 语义分析 -> 字节码生成
1. 词法分析
词法分析逐字节读取java源代码,根据关键字识别出符合规范的Token流。
这么说可能有点具体,当然就是把一段java代码分解成一个个的单词。
public class SimpleTest{
// 域,方法代码块等等
}
会分解成不同的Token,第一个是关键字public,对应的Token类是:Token.PUBLIC。 同样,class关键字对应:Token.ClASS
根据这个Token流程,编译器完成了理解Java语言的第一步。 就像你看到一句话,我想喝水,大脑第一步就是把这句话分解成:我,要,喝水。
2.句型分析
句子解析器会读取Token流,然后生成句子树,类似右图
图片来源RednaxelaFX专家博文
此时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语句javac源码编译javac源码编译,学生将完成变量的手动转换,主要是手动装箱和拆箱等。
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."":(I)V
8: invokevirtual #4 // Method java/lang/Integer.intValue:()I
11: istore_1
12: return
其他代码不用先了解,看invokevirtual#4//Methodjava/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类来遍历句子树
遍历并生成类似右图的字节码
图片来自RednaxelaFX大师的博文
遍历句子树后,得到JVM字节码,可以将字节码交给JVM执行
这里有一点要解释一下。 在编译后的以class为后缀的文件中,字节码不仅包括方法体中的各种指令,还包括常量池、标志位、幻数等二补数据。
如果你想了解JVM字节码,可以参考下面的文章:
如何理解ByteCode、IL、汇编等低级语言与下层语言之间的对应关系? -RednaxelaFX 的回答 - 知乎
参考:
《深入解析JavaWeb技术内幕》——徐凌波
《深入理解Java虚拟机》——周志明