二、【深入了解java虚拟机】class文件的生成-----javac编译器

一、概述

大家都知道 java是一种跨平台的语言,它的平台无关性让java在计算机上运行时不受平台的约束,可以做到一次编译,到处执行。这也是java能够迅速崛起并长久不衰的一个重要原因。而对于Java的平台无关性的支持,就像对安全性和网络移动性的支持一样,是分布在整个Java体系结构中的。其中扮演者重要的角色的有Java语言规范、Class文件、Java虚拟机(JVM)等。下面通过阅读源码的方式去了解java源码文件编译成class文件的过程。

本文是本人根据对源码的理解及相关资料学习成果,在理解上可能会存在一些错误,如果表述或者理解错误的地方能够给予指正,十分感谢!

二、编译过程

和其他高级语言的编译过程一样,java源码的编译大致也可分为:词法分析、语法分析、语义分析、生成字节码文件这几个过程,但由于语言的特性,在这个过程中还会插入一些编程语言特有的处理,java在语法分析的过程中会去处理插入式注解和解语法糖等操作。而这整个编译的过程是用javac编译器来完成的,整个过程大致分为:填充符号表、插入式注解处理、分析及生成字节码

2.1 jdk 源码下载及环境搭建

Open JDK源码下载地址: https://hg.openjdk.org/jdk下载完成以后解压导入Idea即可。
环境搭建:
1、将下载完成的 zip包解压,导入IDEA中
2、修改Settings->Build,Execution,Deployment->Debugger->Stepping设置,将Do not step into the classes去勾选。
在这里插入图片描述
3、然后 Project Structure->Platform Settins->SDKs中将原来默认的源码包删除,替换为源码目录。
在这里插入图片描述
在这里插入图片描述
此时jdk源码已经配置成功了。

2.2 填充符号表

在javac的编译过程中,填充符号表的过程包含了 词法解析、语义解析、生成抽象语法树的几个过程。
整个编译的过程从 com.sun.tools.javac.main compile() 方法开始,从源码中可以看到 先执行

  • parseFiles()方法进行词法和语法分析。
  • 然后在enterTrees()中填充语法树。
  • 在processAnnotations()方法中进行注解的处理;

在这里插入图片描述##### 2.2.1词法分析
词法分析就是将源代码中的字符读取(按照空格分隔),然后将其标记成为集合即 token集合,它是程序中编写的最小单元(例如:java语法中的关键字、运算符、类名、方法名等)。
词法分析的工作由 com.sun.tools.javac.parser.Scanner类完成,通过InitialFileParser.instance() 获取
JavaCompiler 类
在这里插入图片描述在这里插入图片描述
通过构造器,构造ParserFactory,
在这里插入图片描述
在执行JavaCompiler .parse() 方法时,调用ParserFactory.newScanner(),将数据装换为令牌流,再读取处理。
在这里插入图片描述
在这里插入图片描述

2.2.2 语法分析

在词法分析将所有的字符解析成为token之后,就进入语法分析阶段。在这个阶段需要将token解析构建成一颗抽象的语法树(Abstract Syntax Tree,AST,描述一个结构正确的源程序,程序语言结构的树形表示),在IDEA中我们可以下载插件 JDT AST,这样子我们就可以看到我们所写的源码转化成语法树的模样。
在这里插入图片描述
树的每一个节点代表着程序的一个语法结构(Syntax Construct);包、类型、修饰符、运算符、接口、返回值、代码注释等,都可以是一种特定的语法结构;
语法的解析在com.sun.tools.javac.parser.Parser 类中完成, 在parseCompilationUnit() 方法可以看到,程序在解析文件,在组装成 JCTree.JCCompilationUnit 类即所谓的抽象语法树。
在这里插入图片描述
在这里插入图片描述

2.2.5 符号表的填充

符号表(Symbol Table),一组符号地址和符号信息构成的数据结构(包含每个编译单元的抽象语法树的顶级节点和 package-info.java 的顶级节点),类似于 Hash 表的键值对存储结构(也可以是有序符号表、树状符号表、栈结构符号表等形式);符号表在语义分析阶段用于语义检查和产生中间代码,在目标代码生成阶段用于地址分配;
在这里插入图片描述
在这里插入图片描述### 2.3 注解处理
经过了基础的三个分析步骤,最后是编程语言特有的处理,java的最后一步处理事做注解处理,因为java提供了一些语法糖,比如拆箱,foreach、lombok等,他们都有专门的process类对其进行处理,将这些带有这些注解的类进行标记待生成字节码时还原成最原始的写法。
在这里插入图片描述

2.4 语义分析

经过了语法分析获取到抽象语法树和符号表,但是这个过程的产物无法保证程序的语义满足java的语法,语义分析的职责就是对抽象语法树上的结构结合符号表去验证程序的语法,上下文关联的准确性。包括:类型检查、控制流检测、数据流检测等。
在这里插入图片描述
这个过程可分为:

  • attribute():标注检查
  • flow():控制流及数据流分析
  • desugar():解语法糖
  • generate():生成字节码
2.4.1 标注检查

该过程主要检查变量在被使用的过程中有没有被声明,变量和赋值之间的数据类型是都匹配,引用的类是都导入、声明的方法及变量的访问类型是否合法等。在这个过程还会降声明的常量进行折叠。

2.4.2 控制流及数据流分析

这个过程只要检查的有

  • 所有非void 方法的所有分支都有返回值
  • 已经声明抛出异常的方法是否有做相对应的处理
  • 局部变量在使用前是都已经初始化
  • fianl变量是都被重复赋值和语句是都可达等
2.4.3 解语法糖

语法糖只的是编程语言为了能让开发者更方便的使用,所定制的一些语法规则,但是在底层编译时,会将这些语法转义成最原始的语法,比如变长参数、自动装箱拆箱、lombok 等。

2.5 生成字节码

经过了上面一些处理过程,就会生成一颗语法树,生成字节码的过程就是将语法树和符号表转化成字节码指令写到磁盘,在这个过程中还有会一些代码添加和转换的工作。
例如 :
1、语法树中添加实例构造器 () 和类构造器 ();编译器会把语句块(() 的是{}块,() 的是static {} 块)、变量初始化(实例变量和类变量)、调用父类的实例构造器(只有 (),() 中无须调用父类的 (),但经常会生成调用 java.lang.Object 的 () 的代码)等操作收敛到 () 和 (),并保障一定顺序执行(先父类实例构造器、再初始化变量、最后语句块);

javac 的字节码生成由 com.sun.tools.javac.jvm.Gen 类实现;将填充了所有信息的符号表输出到 Class 文件由 com.sun.tools.javac.jvm.CLassWriter 类实现;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Resean0223

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值