其实就是我们前端的编译过程,是通过javac(编译器)把java文件变成.class字节码文件。
javac HelloWorld.java
javap -verbose HelloWorld.class
如上图所示,这里给出一个javac 源码中的 JavaCompiler.java
public void compile(List<JavaFileObject> sourceFileObjects, List<String> classnames, Iterable<? extends Processor> processors) {
try {
// 准备过程:初始化插入式注解处理器
initProcessAnnotations(processors);
// These method calls must be chained to avoid memory leaks
delegateCompiler = // 过程 2:执行注解处理器
processAnnotations(
// 过程 1.2:输出到符号表
enterTrees(
stopIfError(CompileState.PARSE,
// 过程 1.1:词法分析、语法分析
parseFiles(sourceFileObjects))),
classnames);
// 过程 3:分析及字节码生成
delegateCompiler.compile2();
delegateCompiler.close();
elapsed_msec = delegateCompiler.elapsed_msec;
} catch (Abort ex) {
if (devVerbose)
ex.printStackTrace(System.err);
} finally {
if (procEnvImpl != null)
procEnvImpl.close();
}
}
按照上图,逐步分析
1词法分析器
读取源代码,一个字节一个字节读取出来,找到这些词法中的语句比如:访问修饰符、类和类名、条件语句、循环结构、基础的语法等等。
结论:是将源代码的字符流转变为标记(Token)集合的过程,单个字符是程序编写时的最小元素,但标记才是编译时的最小元素。关键字、变量名、字面量、运算符都可以作为标记,如下代码:
int a = b + 2;
这句代码中就包含了7个标记,分别是int、a、=、b、+、2、;、虽然关键字int由3个字符构成,但是它只是一个独立的标记,不可以再拆分。在Javac的源码中,词法分析过程由 com.sun.tools.javac.parser.Scanner类来实现。
真正完成解析的是 JavaTokenizer.java的readToken();
方法
2语法分析器
根据Token集合生成抽象语法树,抽象语法树(Abstract Syntax Tree,AST)是一 种用来描述程序代码语法结构的树形表示方式,抽象语法树的每一个节点都代表着程序代码中的一个 语法结构(SyntaxConstruct),例如包、类型、修饰符、运算符、接口、返回值甚至连代码注释等都 可以是一种特定的语法结构.
上述这段代码生成的抽象语法树如下( IDEA JDT AstView 插件可以查看抽象语法树):
上述抽象语法树在Java中使用com.sun.tools.javac.tree.JCTree类来表示。
经过词法和语法分析生成语法树以后,编译器就不会再对源码字符流进行操作了,后续的操作都建立在抽象语法树之上。
结论:检查Token集合是否符合Java语言规范,有没有语法的错误,一切通过校验后得到一颗抽象的语法树。
例如:if 后面是否跟着boolean表达式 ,Java 关键字是否正确等等。
3语义分析
经过语法分析之后,编译器获得了程序代码的抽象语法树表示,抽象语法树能够表示一个结构正确的源程序,但无法保证源程序的语义是符合逻辑的;
结论:而语义分析的主要任务则是对结构上正确的源程序进行上下文相关性质的检查,将比较复杂的语法语义转化成简单的语法,譬如进行变量类型检查、控制流检查、数据流检查。
对我们刚刚生成那颗抽象语法解析树进行变量类型检查、控制流检查、数据流检查,解语法糖。
解语法糖
通常来说使用语法糖能够减少代码量、增加程序的可读性,从而减少程序代码出错的机会。“低糖”的语法让Java程序实现相同功能的代码量往往高于其他语言,通俗地说 就是会显得比较“啰嗦”,所以才会出现 Kotlin。
解语法糖的过程由desugar()方法触发,在com.sun.tools.javac.comp.TransTypes类 和com.sun.tools.javac.comp.Lower类中完成。
4字节码生成
字节码生成是Javac编译过程的最后一个阶段,在Javac源码里面由com.sun.tools.javac.jvm.Gen类来 完成。字节码生成阶段不仅仅是把前面各个步骤所生成的信息(语法树、符号表)转化成字节码指令写到磁盘中,编译器还进行了少量的代码添加和转换工作。
比如:
- 将static语句块、static变量收敛到方法中
- 将实例变量初始化、调用父类构造器收敛到方法
- 程序优化,比如将字符串的+操作替换成StringBuilder的append
完成了语法树的遍历和调整以后,就会填充了所有信息的符号表交给com.sun.tools.javac.jvm.ClassWriter类,最后由该类的writeClass()方法输出字节码。
结论:代码生成器的结果就是生成符合Java虚拟机规范的字节码。
5 Class File 解析
这个里面东西属实太多而且繁杂,大家有兴趣了可以看看我的JVM专栏里的纸质笔记
链接: JVM学习专栏
链接: JVM学习笔记-Class类文件结构介绍——(纸质笔记)
链接: JVM学习笔记-Class类文件结构-魔数,版本号,常量池——(纸质笔记)
链接: JVM学习笔记-Class类文件结构-访问标志,类索引,父类索引,接口索引集合——(纸质笔记)