第二章 Javac编译原理
注:本文主要记录自《深入分析java web技术内幕》"第四章 javac编译原理"
1、javac作用
- 将*.java源代码文件转化为*.class文件
2、编译流程
流程:
- 词法分析器:将源码转换为Token流
- 将源代码划分成一个个Token(Token包含的元素类型看3.2)
- 语法分析器:将Token流转化为语法树
- 将上述的一个个Token组成一句句话(或者说成一句句代码块),检查这一句句话是不是符合Java语言规范
- 语义分析器:将语法树转化为注解语法树
- 将复杂的语法转化成简单的语法(eg.注解、foreach转化为for循环)并做一些检查,添加一些代码
- 代码生成器:将注解语法树转化为字节码
3、词法分析
3.1、作用
- 将源码转换为Token流。
3.2、流程
一个字节一个字节的读取源代码,形成规范化的Token流。规范化的Token包含:
- java关键词:package、import、public、class、int等
- 自定义单词:包名、类名、变量名、方法名
- 符号:=、;、+、-、*、/、%、{、}等
3.3、示例
代码:
package compile; /**
* 词法
*/
public class Cifa {
int a;
int c = a + 1;
}
以上代码转化为的Token流:
说明:完成以上示例的是JavacParser的parseCompilationUnit()方法,源代码见文章开头的书籍。
注意:上边的token流符合java语言规范。
3.4、疑问
- 怎样判断package是java关键词还是自定义变量?
- JavacParser会根据java语言规范来控制什么顺序、什么地方出现什么Token(这个查看parseCompilationUnit()源码就知道了),所以package在文件的最开头出现,我们会知道是一个Token.PACKAGE类型,而非自定义的Token.IDENTIFIER类型。
- 一条实践:在编写程序的时候,不要用java关键词来定义变量名、类名、包名、方法名,而是采取一定有意义的单词来定义,当然,你再eclipse中编写代码的时候,如果使用了java关键词来定义变量,eclipse会提醒你这是一个错误的定义。
- 怎样确定package是一个Token,而packa不是?
- 我的理解是,主要看空格和符号(符号见3.2),对于package是一个单词,中间没有空格也没有符号,所以是一个Token
- 一条实践:在编写代码时,例如:int a = b + c;//a与=中间有一个空格、=与b之间有一个空格、b与+之间有一个空格、+与c之间有一个空格,当然,这里没有空格也行,因为每一个变量之间正好都是由符号来隔开的,但是之前看了一个视频说,如果上边这句话没有这些空格的话,可能编译不通过,所以我们最好还是加上空格,当然加上空格后显得整个代码也清晰。
4、语法分析
4.1、作用
- 将进行词法分析后形成的Token流中的一个个Token组成一句句话,检查这一句句话是不是符合Java语言规范。
4.2、语法分析三部分:
- package
- import
- 类(包含class、interface、enum),一下提到的类泛指这三类,并不单单是指class
4.3、示例
代码:
package compile; /**
* 语法
*/
public class Yufa {
int a;
private int c = a + 1; //getter
public int getC() {
return c;
}
//setter
public void setC(int c) {
this.c = c;
}
}
最终语法树:
说明:
- 每一个包package下的所有类都会放在一个JCCompilationUnit节点下,在该节点下包含:package语法树(作为pid)、各个类的语法树
- 每一个从JCClassDecl发出的分支都是一个完整的代码块,上述是四个分支,对应我们代码中的两行属性操作语句和两个方法块代码块,这样其实就完成了语法分析器的作用:将一个个Token单词组成了一句句话(或者说成一句句代码块)
- 在上述的语法树部分,对于属性操作部分是完整的,但是对于两个方法块,省略了一些语法节点,例如:方法修饰符public、方法返回类型、方法参数。
疑问:
import节点的语法树与package的相似,但是import语法树放在了哪一个地方?
5、语义分析
5.1、作用
- 将语法树转化为注解语法树
5.2、步骤
- 添加默认的无参构造器(在没有指定任何有参构造器的情况下)
- 处理注解
- 标注:检查语义合法性、进行逻辑判断
- 检查语法树中的变量类型是否匹配(eg.String s = 1 + 2;//这样"="两端的类型就不匹配)
- 检查变量、方法或者类的访问是否合法(eg.一个类无法访问另一个类的private方法)
- 变量在使用前是否已经声明、是否初始化
- 常量折叠(eg.代码中:String s = "hello" + "world",语义分析后String s = "helloworld")
- 推导泛型方法的参数类型
- 数据流分析
- 变量的确定性赋值(eg.有返回值的方法必须确定有返回值)
- final变量只能赋一次值,在编译的时候再赋值的话会报错
- 所有的检查型异常是否抛出或捕获
- 所有的语句都要被执行到(return后边的语句就不会被执行到,除了finally块儿)
- 进一步语义分析
- 去掉永假代码(eg.if(false))
- 变量自动转换(eg.int和Integer)
- 去掉语法糖(eg.foreach转化为for循环,assert转化为if,内部类解析成一个与外部类相关联的外部类)
- 最后,将经过上述处理的语法树转化为最后的注解语法树
6、生成字节码
6.1、作用
- 将注解语法树转化成字节码,并将字节码写入*.class文件。
6.2、步骤
- 将java的代码块转化为符合JVM语法的命令形式,这就是字节码
- 按照JVM的文件组织格式将字节码输出到*.class文件中
具体的源代码与步骤查看com.sun.tools.javac.jvm.Gen类与《分布式Java应用:基础与实践》P42
6.3、class文件包含的内容
在生成的*.class文件中不只包含字节码信息,具体包含:
- 结构信息
- class文件格式版本号
- 各部分的数量与大小
- 元数据
- 类、父类、实现接口的声明信息
- 属性声明信息
- 方法声明信息
- 常量池
- 方法信息
- 字节码
- 异常处理器表
- 局部变量区的大小
- 操作数栈的大小
- 操作数栈的类型记录
- 调试用符号信息
这里提到的局部变量区和操作数栈组成了了方法栈,可以参看第一章 JVM内存结构
总结:
对于编译这一块儿,我们在实际操作中不会直接去操作这些代码,不像类加载器机制,我们可能需要自己编写类加载工具,也不像Java内存管理那样,我们会直接在服务器配置堆栈方法区空间、配置GC收集器等,但是了解javac编译,对于我们了解以后的类文件结构、类加载机制有一定的帮助,也有利于我们掌握整个Java代码的执行流程,对于我们了解编译期间编译器做的一些检查工作也有很大帮助,了解这些检查工作有利于我们在写代码的时候更加小心,例如,检查型异常都需要捕获或抛出,每一条语句都要被执行到(即可达)等。虽然,这些工作eclipse会在我们写代码的时候为我们自动去检查,包括检查语句是否可达,但是了解这些还是有好处的。
第二章 Javac编译原理的更多相关文章
- 第四章 Javac编译原理
4.1 Javac是什么 是一种编译器,将JAVA源代码(.java文件)语言先转化成JVM能够识别的一种语言(.class文件),然后由JVM将JVM语言再转化成当前机器可以识别的机器语言. 4.2 ...
- 第四章 Javac编译原理(待续)
Javac是什么 Javac编译器的基本结构 Javac工作原理分析 设计模式解析之访问者模式
- Knowledge Point 20180303 对比编译器、解释器与Javac编译原理
编译器与Javac编译原理 在前文我们知道了Java是一种编译语言和解释语言,它的源代码经过编译器Javac编译为能够被JVM识别的二进制语言,然后JVM将其解释为能够被平台识别的机器语言.那么什么是 ...
- Javac编译原理 《深入分析java web 技术内幕》第四章
javac编译的四个主要的流程: 词法分析器:将源码转换为Token流 将源代码划分成一个个Token(找出java语言中的关键字) 语法分析器:将Token流转化为语法树 将上述的一个个Token组 ...
- javac编译原理(一)
我们都知道,计算机只能识别二进制语言,是不能直接识别java c c++等高级语言的.将高级语言转化成计算机可以是别的二进制语言,这个过程就叫编译. 有次面试,面试官问了一道“java的编译原理是什么 ...
- <Mastering KVM Virtualization>:第二章 KVM内部原理
在本章中,我们将讨论libvirt.QEMU和KVM的重要数据结构和内部实现.然后,我们将深入了解KVM下vCPU的执行流程. 在这一章,我们将讨论: libvirt.QEMU和KVM的内部运作方式. ...
- Javac 编译原理
写在前面 JDK & JRE JRE(Java Runtime Enviroment)是Java的运行环境.面向Java程序的使用者,而不是开发者.如果你仅下载并安装了JRE,那么你的系统只 ...
- javac编译原理
javac编译器的作用就是将符合java语言规范的源代码转化成符合java虚拟机规范的java字节码 经历:词法分析器->语法分析器->语义分析器->编译字节码 四个过程生成字节码文 ...
- 第二章 rsync服务原理
一.备份 1.什么是备份? 1)把重要的数据或者文件再次复制一份并保存下来 2.为什么要做备份? 1)数据的重要性 2)为了出现故障,恢复数据 3.能不能不备份? 1)重要的数据一定要备份 2)不重要 ...
随机推荐
- python面向对象中类对象、实例对象、类变量、实例变量、类方法、实例方法、静态方法
1. 类对象和实例对象 Python中一切皆对象,Python类本身也是一种对象,类定义完成后,会在当前作用域中定义一个以类名为名字的命名空间.类对象具有以下两种操作: 可以通过“类名()”的方式实例 ...
- Elasticsearch源码分析 | 单节点的启动和关闭
本文主要简要介绍Elasticsearch单节点的启动和关闭流程.Elasticsearch版本:6.3.2 相关文章 1.Google Guice 快速入门 2.Elasticsearch 中的 G ...
- [leetcode trie]212. Word Search II
Given a 2D board and a list of words from the dictionary, find all words in the board. Each word mus ...
- 虚拟多Mac地址工具Multimac
虚拟多Mac地址工具Multimac Mac地址采用唯一标识标记网络的各种设备.在同一个时间内,Linux系统中的网卡只能使用一个Mac地址.在渗透测试中,为了隐藏自己的身份,往往需要以不同的Ma ...
- 多套方案来提高python web框架的并发处理能力
Python常见部署方法有 : fcgi :用spawn-fcgi或者框架自带的工具对各个project分别生成监听进程,然后和http 服务互动 wsgi :利用http服务的mod_wsgi模 ...
- ARC 067 E - Grouping
题面在这里! 很显然是个暴力dp. 我们先枚举一下 队伍人数的种类,然后再逆序枚举一下dp数组里的总人数(顺序就会算重),最后枚举一下这种队伍的数量,之后就可以O(1)算方案了. 具体的,O(1)算方 ...
- Vi 学习 笔记
rails server -p 端口名 // 切换端口 Vi 常用指令: mkdir filename //创建文件 mv filename1 filename2 // 文件重命名 rm filena ...
- OpenGL ES 3.0 图元装配
1. 前言 之前已经把纹理的渲染给弄出来了,但是又遇到一个新的问题,那就是图元装配,比如说我已经把图片给显示出来了,但是呢,并没有做到让它显示到具体的位置,而跟这个位置相关的则需要靠图元装配. 图元装 ...
- 从零开始搭建linux下laravel 5.5所需环境(二)
我们已经装好了nginx,现在我们开始装mysql和php 我们同样使用yum来安装. 先安装MySQL软件(客户端.服务器端.依赖库) yum install -y mysql mysql-serv ...
- Cocos2d-x移植android增加震动效果
cpp部分通过jni调用java静态函数 头文件: #include <jni.h> #include "cocos2d.h" #include "platf ...