文章目录

  • 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编译原理简介相关推荐

  1. 第二章 Javac编译原理

    注:本文主要记录自<深入分析java web技术内幕>"第四章 javac编译原理" 1.javac作用 将*.java源代码文件转化为*.class文件 2.编译流程 ...

  2. 浅入浅出Javac编译原理——爪哇岛探险(1)

    浅入浅出Javac编译原理 Java语言是当今程序员中使用最广的语言,不光是从语言本身来说,还包括了与Java相关的一些概念.例如JDK,J2EE,JVM等等.还不断有新的语言出现,如groove,s ...

  3. 编译原理代码生成器java_浅入浅出Javac编译原理

    浅入浅出Javac编译原理 Java语言是当今程序员中使用最广的语言,不光是从语言本身来说,还包括了与Java相关的一些概念.例如JDK,J2EE,JVM等等.还不断有新的语言出现,如groove,s ...

  4. 简介DOTNET 编译原理 简介DOTNET 编译原理 简介DOTNET 编译原理

    简介DOTNET 编译原理 相信大家都使用过 Dotnet ,可能还有不少高手.不过我还要讲讲Dotnet的基础知识,Dotnet的编译原理. Dotnet是一种建立在虚拟机上执行的语言,它直接生成 ...

  5. JavaWeb技术内幕四:Javac编译原理

    微信公众号[黄小斜]大厂程序员,互联网行业新知,终身学习践行者.关注后回复「Java」.「Python」.「C++」.「大数据」.「机器学习」.「算法」.「AI」.「Android」.「前端」.「iO ...

  6. java编译器 Javac 编译原理

    目录 词法分析器 语法分析器 语义分析器 代码生成器 java源代码(符合语言规范)-->javac-->.class(二进制文件)-->jvm-->机器语言(不同平台不同种类 ...

  7. 浅谈Javac编译原理

    一.javac是什么? 1.javac是一种编译器,能够将一种语言规范转化成另外一种语言规范 2.javac的任务就是将Java源代码转化成JVM能够识别的一种语言(Java字节码),这种字节码不是针 ...

  8. 手撕python_手撕编译器(一)——编译原理简介

    前言 最近一两个月一直在准备秋招的事情,太忙了,学习进度给暂停了下来,然后秋招也快到了尾声,这里抓紧时间把自己想要学的东西学完,这里要做的是研究的斯坦福的cs143,整个的资源可以在edx上搜索到,大 ...

  9. 我看过的编译原理方面的好文章

    本文不定期更新,最后更新于2019-7-6 编译原理 编译原理三大经典书籍(龙书 虎书 鲸书) 前端为什么要会正则表达式 - 知乎 一次性搞懂JavaScript正则表达式之引擎 - 掘金 没有AST ...

最新文章

  1. 面对对象编程——用Python写一个图书管理系统
  2. .prop()与.attr()
  3. php$上传_如何实现PHP上传视频的功能?(图文+视频)
  4. java拉姆达表达式事例,Java Lambda表达式详解和实例
  5. BZOJ 4244 邮戳拉力赛 (DP)
  6. [转]OpenStack的网络模式
  7. Java-Java I/O流解读之java.io.PrintStream java.io.PrintWriter
  8. 每日程序C语言39-不带头结点的头插法创建链表
  9. mysql简单的存储过程实例_mysql存储过程简单实例
  10. 蓝桥杯 ALGO-11算法训练 瓷砖铺放(递归/动态规划)
  11. JDBC06 其他操作及批处理Batch
  12. [附源码]java毕业设计社区医院电子病历系统
  13. 虚拟机安装教程win10_Parallels Desktop如何安装windowns系统?PD虚拟机安装win10系统详细教程
  14. IR2103H桥驱动电路
  15. 【智能优化算法】广义邻域搜索算法(综述)
  16. Windows系统重装Linux系统
  17. 打开mysql 的时候报错_关于mysql的启动报错处理
  18. 现在网络安全员工资一般多少(网络安全员平均工资)
  19. 关于印发《建造师执业资格制度暂行规定》的通知
  20. 用Matlab读取、显示并保存图片

热门文章

  1. 053RINEX中O文件示例说明
  2. 阿里技术:浅谈分库分表那些事儿
  3. AMD3700X变4核8线程解决方法
  4. Spring Boot单元测试方法Failed to load ApplicationContext
  5. performance monitor for mysql_借助zabbix和mysqlperformancemonitor模板实现mysql数据库的监控...
  6. 3.1.3 Fiddler工具详细教程
  7. 2k19一直显示储存到服务器,NBA2K19画面设置保存不了解决办法
  8. C++实现人脸识别(百度云平台)
  9. FFplay文档解读-22-音频过滤器七
  10. 扎克伯格:在改变世界的路上,总有人要阻拦我