javac编译原理简介
文章目录
- 1:javac的七个阶段
- 1.1 第一阶段:parse
- 1.2 第二阶段:enter
- 1.3 第三阶段:process
- 1.4 第四阶段:attr
- 1.4 第五阶段:flow
- 1.5 第六阶段:desugar
- 1.6 第七阶段:generate
javac这种将源代码转化为字节码的过程在编译原理上属于前端编译,不涉及相关代码的生成和优化。
JDK中的javac本身是用Java语言编写的,在某种意义上实现javac语言自举。javac没有使用类似的YACC和Lex这样的生成器工具,所有词法分析和语法分析等功能都是自己实现,代码精简高效
通过以下学习,我们可以知道javac编译过程的七个阶段和各阶段作用
1:javac的七个阶段
1 ) parse:读取java源文件,做词法分析(LEXER)和语法分析(PARSER)
2 ) enter: 生成符号表
3 ) process: 处理注解
4) attr: 检查语义合法性、常量折叠
5) flow: 数据流分析
6) desugar:去除语法糖
7 ) generate:生成字节码
接下来我们逐一介绍各项内容。
1.1 第一阶段:parse
parse阶段的主要作用是读取.java源文件并做词法分析和语法分析
词法分析( lexical analyze)将源代码拆分为一个个词法记号(Token),这个过程又被称为扫描( scan),比如代码i=2 +2在词法分析时会被拆分为五部分: i、=、2、+、2。这个过程会将空格、空行、注释等对程序执行没有意义的部分排除。词法分析与我们理解英语的过程类似,比如英语句子“i am studying"在我们大脑中会被拆分为i、am、studying三个单词。
javac中的词法分析由com.sun.tools.javac.parser.Scanner 实现,以语句“intk=i+j;"
Scanner会读取源文件中的内容,将其解析为Java语言的Token序列,这个过程如下图所示。
词法分析之后是进行语法分析( syntax analying),语法分析是在词法分析的基础上分乐单间之间的关系,将其转换为计算机易于理解的形式,生成抽象语法树( Abttct SyntaxTree,AST)。AST是一个树状结构,树的每个节点都是一个语法单元,抽象语法树是后续的语义分析、语法校验、代码生成的基础。与其他多数语言一样,javac也是使用递归下降法来生成抽象语法树。如下图
解析CSV,JSON,XML本质也是语法分析和一部分语义分析的过程
1.2 第二阶段:enter
enter阶段的主要作用是解析和填充符号表(symbol table),主要由com.suntools.javac.comp.Enter和com.sun tools javac.comp.MemberEnter类来实现。符号表是由标识符、标识符类型、作用域等信息构成的记录表。在遍历抽象语法树遇到类型、变量、方法定义时,会将它们的信息存储到符号表中,方便后续进行快速查询。
int x=5;//定义int型字段,初始化为5public long add(long a,long b){return a+b;}
javac使用Symbol类来表示符号,每一个符号都包含名称,类别和类型这三个关键属性。如下
name:表示符号名,如上的x,add都是符号名
kind:表示符号类别,上面的x符号类别是Kinds.VAR,表示这是一个变量符号,add的符号类型是Kinds,MTH,表示这是一个方法符号
type:表示符号类型,上面的x的符号类型是int,add方法的符号类型是null
Symbol类是一个抽象类,常见的有VarSymbol,MethodSymbol
Symbol定义了符号是什么,作用域则指定了符号的有效范围,由com.sun tools javac.code.Scope,如下
public void test(){int x=0;}public void test1(){int x=0;}
test函数和test1函数都定义了一个名为x的int变量,这两个变量可以独立使用,在超出各自的方法体作用域后就对外不可见,外部也访问不到
注意:enter阶段除了上述生成符号表,还会在类文件中没有默认构造方法的情况下,添加< init >构造方法等
1.3 第三阶段:process
process用来做注解的处理,这个步骤由com.sun.tools.javac processing JavacProcessing-Environment类完成。**从JDK6开始,javac 支持在编译阶段允许用户自定义处理注解,大名鼎鼎的lombok框架就是利用了这个特性,通过注解处理的方式生成目标class 文件,比在运行时反射调用性能明显提升。**会在以后单独进行介绍,这里不再展开。
1.4 第四阶段:attr
attrt 阶段是语义分析的一部分, 主要由com.sun.tools.javac comp.Attrt类实现,这个阶段会做语义合法性检查、常量折叠等,由com.sun.tools.javac.comp 包下的Check、Resolve、ConstFold、Infer 几个类辅助实现。
- com. sun. tools. javac. comp.Check类的主要作用是检查变量类型、方法返回值类型是否合法,是否有重复的变量、类定义等,比如下面这些场景。
1 )检查方法返回值是否与方法声明的返回值类型一致,以下面的代码为例。
public int foo() {return "hello";
}
这个检查由com.sun.tosjavac comp.Check类的checkType 完成,这个方法的定义如下。
Type checkType(final DiagnosticPosition pos, final Type found, final Type req,final CheckContext checkContext){
当抽象语法树遍历到return “hello”;对应的节点时,得到的found参数值为对象类型String,req类型为int,返回值类型和方法声明不一致
2)检查是否有重复的定义,比如检查同一个类中是否存在相同的签名方法。校验逻辑由Check类的checkUnique方法实现。以下面的代码为例。
public void test()
public void test(
在编译时会出现重复定义的错误.
- com.sun. tools.javac .comp.Resolve类的主要作用是:
检查变量、方法、类访问是否合法,比如pivate 方法的访问是否在方法所在类中访问等。
为重载方法调用选择最具体的方法。
以下面的代码为例:
publie static void method(objecet obj) {System. out.print1n( "method # object");
}
public static void method(String obj) {System.out.printIn("method # String");
}
public static void main(String[] args) {method(null);
}
在Java中允许方法重载( overload),但要求方法签名不能一样。 调用method(null)实示是调用第二个方法输出“method # String”, javac 在编译时会推断出最具体的方法,方法的选择在Resolve 类的mostSpecific方法中完成。第二个方法的入参类型String 是第一个方法的入参类型Object的子类,会被javac认为更具体。
- com.sun.tools.javac. comp.ConstFold类的主要作用是在编译期将可以合并的常量合并,比如常量字符串相加,常量整数运算等,以下面的代码为例。
public void test() {int x=1+2;String y = "hel" + "lo";int z=100 / 2;
对应的字节码如下所示:
0: iconst 3
1: istore 1
2: ldc #2 // String hello
4: astore_ 2
5: bipush 50
7: istore_ 3
8: return
可以看到编译后的字节码已经将常量进行了合并,javac 没有理由把这些可以在编译期完成的计算留到运行时。
- com.sun.tools javac.comp.Infer类的主要作用是推导泛型方法的参数类型,比较简单,这里不再赘述。
1.4 第五阶段:flow
阶段主要用来处理数据流分析。主要由com.sun.tools.javac.comp.Flow类实现很多编译期的校验在这个阶段完成,
下面列举几常见的场景。
1)检查非void方法是否所有的分支都有返回值,以下面的代码为例:
public boolean test(int x) (
if(x==0)
return true
/注释掉这个return
// returnfalse;
上面的代码编译报错
2)检查受检异常(checked eception)是否被捕获或者显式抛出,以下面的代码为例。
public void test() {throw new FileNotFoundException();
}
上面的代码编译报错
3)检查局部变量使用前是否被初始化。Java中的成员变量在未赋值的情况下会赋值为默认值,但是局部变量不会,在使用前必须先赋值,以下面的代码为例。
public void test() {int x;
inty=x+1;
System. out.println(y);}
上面的代码编译报错如下:
error: variable x might not have been initializedinty=x+1;
4)检查final变量是否有重复赋值,保证fnal 的语义,以下面的代码为例。
public void test(final int x) {x=1;
System. out.println(x);
}
上面的代码编译报错
1.5 第六阶段:desugar
下面某种意义上来说都算是语法糖:泛型,内部类,foreach语句,原始类型和包装类型之间的隐式转换,字符串和枚举的switch-case实现,后缀运算符(i++和++i)、变长参数等。
desugar的过程就是解除语法糖,主要由com.sun.tools.javac comp.TransTypes类和com.sun.tools. javac .comp.Lower类完成。TransTypes类用来擦除泛型和插入相应的类型转换代码,Lower类用来处理除泛型以外其他的语法糖。
下面列举几个场景
1)在desugar阶段泛型会被擦除,在有需要时自动为原始类和包装类型转化添加拆箱,装箱代码
以下代码示例
public void test() {List<Long> idList = new ArrayList<>();
idList.add(1L);
long firstId =idList.get(0);
}
对应的字节码如下所示:
//执行new ArrayList<>()
0: new #2 // class java/util/ArrayList
3: dup
4: invokespecial #3 // Method java/util/ArrayList. "<init>":()V
7: astore 1
8: aload 1
//把原始类型1自动装箱为Long类型
9: lconst_ 1
10: invokestatic #4 // Method java/ lang/Long.value0f: (J)Ljava/lang/Long;
//执行add调用
13: invokeinterface #5,2 // InterfaceMethod java/util/List. add: (Ljava/lang/object; )Z
18: pop
19: aload 1
// 执行get(0)调用
20: iconst _0
21: invokeinterface #6,2// InterfaceMethod java/uti1/List.get:(I)Ljava/lang/object;
//检查object 对象是否是Long类型
26: checkcast #7 // class java/1ang/Long
// 自动拆箱为原始类型
29: invokevirtual #8 // Method java/ang/Long, longVaue()J
32: lstore. _2
33:return
2)去除逻辑死代码,也就是不可能进入的代码块,例子省略
3)String类,枚举类的switch-case也是在desugar阶段进行的。例子省略
1.6 第七阶段:generate
generate阶段的主要作用是遍历抽象语法树生成最终的Class文件,由com.sun.tools.javac.jvm.Gen类实现
1)初始化块代码并收集到< init>和< clinit >中,以下面的代码为例。
public class MyInit {{System.out. println( "hel1o");
}
public int a = 100;
生成的构造器方法对应的字节码如下所示。
public MyInit();
0: aload_0
1: invokespecial #1// Method java/lang/object. "<init>":()V
4: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
7: ldc #3 // String hello
9: invokevirtual #4 // Method java/ io/PrintStream. print1n: (Ljava/lang/String; )V
12: aload 0
13: bipush 100
15: putfield #5
18: return
可以看到,编译器会自动帮忙生成一个构造器方法< init >,没有写在构造器方法中的字段初始化、初始化代码块都被收集到了构造器方法中,翻译为Java源代码如下所示。
public class MyInit {public int a;
public MyInit() {System. out . println("hello");
this.a = 100;
}
与static修饰的静态初始化的逻辑一样,javac 会将静态初始化代码块和静态变量初始化收集到< clinit >方法中。
2)把字符串拼接语句转换为StringBuilder.append 的方式来实现,比如下面的字符串x和y的拼接代码。
public void foo(String x,String y) {String ret=x+y;
System. out . println(ret);
}
在generate阶段会被转换为下面的代码。
public void foo(String x!string y) {String ret = newstringBuilder().appenda(s).append(s2).tostring();system.out.printIn(ret);
}
3)为synchronized关键字生成异常表,保证monitorenter,monitorexit指令可以成对调用。
4)switch-case实现tableswitch和lookupswitch指令的选择,根据稀疏程度来选择
如下是在com.sun.tools.javac.jvm.Gen.java的源代码摘取的两个指令逻辑选择的一部分
// Determine whether to issue a tableswitch or a lookupswitch// instruction.long table_space_cost = 4 + ((long) hi - lo + 1); //hi代表case值的最大值,lo表示case值的最小值long table_time_cost = 3; // comparisonslong lookup_space_cost = 3 + 2 * (long) nlabels;// nlables等于case个数long lookup_time_cost = nlabels;int opcode =nlabels > 0 &&table_space_cost + 3 * table_time_cost <=lookup_space_cost + 3 * lookup_time_cost?tableswitch : lookupswitch;
javac编译原理简介相关推荐
- 第二章 Javac编译原理
注:本文主要记录自<深入分析java web技术内幕>"第四章 javac编译原理" 1.javac作用 将*.java源代码文件转化为*.class文件 2.编译流程 ...
- 浅入浅出Javac编译原理——爪哇岛探险(1)
浅入浅出Javac编译原理 Java语言是当今程序员中使用最广的语言,不光是从语言本身来说,还包括了与Java相关的一些概念.例如JDK,J2EE,JVM等等.还不断有新的语言出现,如groove,s ...
- 编译原理代码生成器java_浅入浅出Javac编译原理
浅入浅出Javac编译原理 Java语言是当今程序员中使用最广的语言,不光是从语言本身来说,还包括了与Java相关的一些概念.例如JDK,J2EE,JVM等等.还不断有新的语言出现,如groove,s ...
- 简介DOTNET 编译原理 简介DOTNET 编译原理 简介DOTNET 编译原理
简介DOTNET 编译原理 相信大家都使用过 Dotnet ,可能还有不少高手.不过我还要讲讲Dotnet的基础知识,Dotnet的编译原理. Dotnet是一种建立在虚拟机上执行的语言,它直接生成 ...
- JavaWeb技术内幕四:Javac编译原理
微信公众号[黄小斜]大厂程序员,互联网行业新知,终身学习践行者.关注后回复「Java」.「Python」.「C++」.「大数据」.「机器学习」.「算法」.「AI」.「Android」.「前端」.「iO ...
- java编译器 Javac 编译原理
目录 词法分析器 语法分析器 语义分析器 代码生成器 java源代码(符合语言规范)-->javac-->.class(二进制文件)-->jvm-->机器语言(不同平台不同种类 ...
- 浅谈Javac编译原理
一.javac是什么? 1.javac是一种编译器,能够将一种语言规范转化成另外一种语言规范 2.javac的任务就是将Java源代码转化成JVM能够识别的一种语言(Java字节码),这种字节码不是针 ...
- 手撕python_手撕编译器(一)——编译原理简介
前言 最近一两个月一直在准备秋招的事情,太忙了,学习进度给暂停了下来,然后秋招也快到了尾声,这里抓紧时间把自己想要学的东西学完,这里要做的是研究的斯坦福的cs143,整个的资源可以在edx上搜索到,大 ...
- 我看过的编译原理方面的好文章
本文不定期更新,最后更新于2019-7-6 编译原理 编译原理三大经典书籍(龙书 虎书 鲸书) 前端为什么要会正则表达式 - 知乎 一次性搞懂JavaScript正则表达式之引擎 - 掘金 没有AST ...
最新文章
- 面对对象编程——用Python写一个图书管理系统
- .prop()与.attr()
- php$上传_如何实现PHP上传视频的功能?(图文+视频)
- java拉姆达表达式事例,Java Lambda表达式详解和实例
- BZOJ 4244 邮戳拉力赛 (DP)
- [转]OpenStack的网络模式
- Java-Java I/O流解读之java.io.PrintStream java.io.PrintWriter
- 每日程序C语言39-不带头结点的头插法创建链表
- mysql简单的存储过程实例_mysql存储过程简单实例
- 蓝桥杯 ALGO-11算法训练 瓷砖铺放(递归/动态规划)
- JDBC06 其他操作及批处理Batch
- [附源码]java毕业设计社区医院电子病历系统
- 虚拟机安装教程win10_Parallels Desktop如何安装windowns系统?PD虚拟机安装win10系统详细教程
- IR2103H桥驱动电路
- 【智能优化算法】广义邻域搜索算法(综述)
- Windows系统重装Linux系统
- 打开mysql 的时候报错_关于mysql的启动报错处理
- 现在网络安全员工资一般多少(网络安全员平均工资)
- 关于印发《建造师执业资格制度暂行规定》的通知
- 用Matlab读取、显示并保存图片
热门文章
- 053RINEX中O文件示例说明
- 阿里技术:浅谈分库分表那些事儿
- AMD3700X变4核8线程解决方法
- Spring Boot单元测试方法Failed to load ApplicationContext
- performance monitor for mysql_借助zabbix和mysqlperformancemonitor模板实现mysql数据库的监控...
- 3.1.3 Fiddler工具详细教程
- 2k19一直显示储存到服务器,NBA2K19画面设置保存不了解决办法
- C++实现人脸识别(百度云平台)
- FFplay文档解读-22-音频过滤器七
- 扎克伯格:在改变世界的路上,总有人要阻拦我