Android进阶——Javac编译解析

news2024/12/23 7:52:37

Javac编译器

1.Javac的源码与调试

Javac的源码下载地址:Javac的源码下载地址,在Myeclipse中新建项目Compiler_javac,把源码复制到项目中。

Javac的源码目录:

从Sun Javac的代码来看,编译过程大致可以分为3个过程:

Java编译动作的入口类为JavaCompiler,上述3个过程的代码逻辑集中在这个类的compile()和compile2()方法中。源代码如下:

2.解析与填充符号表

2.1词法分析、语法分析

词法分析:是将源代码的字符流转变为标记(Token)集合,单个字符是程序编写过程的最小元素,而标记则是编译过程的最小元素,关键字、变量名、字面量、运算符都可以看成标记。

在Javac的源码中,词法分析过程由com.sun.tools.javac.parser.Scanner类实现。

语法分析:是根据Token序列构造抽象语法树的过程,抽象语法树(Abstract Syntax Tree,AST)是一种用来描述程序代码语法结构的树形表示方式,语法树的每一个节点都代表着程序代码中的语法结构(Construct),例如包、类型、修饰符、运算符、接口、返回值甚至代码注释都可以是一个语法结构。

在Javac的源码中,语法分析过程由com.sun.tools.javac.parser.Parser类实现。

这个阶段产生的抽象语法树由com.sun.tools.javac.tree.JCTree类表示,经过这个步骤之后,编译器就基本不会再对源码文件操作了,后续的操作都建立在抽象语法树之上。

在Myeclipse中安装ASTView插件的下载地址:ASTView插件下载地址,安装插件后Window中Show View中选择ASTView。

抽象语法树结构视图如下:

2.2填充符号表

符号表(Symbol Table)是由一组符号地址和符号信息构成的表格,读者可以把它想象成哈希表中K-V值对的形式(实际上符号表不一定是哈希表实现,可以是有序符号表、树状符号表、栈结构符号表等)。

符号表中所登记的信息在编译的不同阶段都要用到。

1)在语义分析中:符号表所登记的内容将用于语义检查(如检查一个名字的使用和原先的说明是否一致)和产生中间代码。

2)在目标代码生成阶段:当对符号名进行地址分配时,符号表是地址分配的依据。

在Javac源码中,填充符号表的过程由com.sun.tools.javac.comp.Enter类实现,此过程的出口是一个待处理列表(To Do List),包含了每一个编译单元的抽象语法树的顶级节点,以及package-info.java(如果存在的话)的顶级节点。

3.注解处理器

JDK1.6提供了一组插入式注解处理器的标准API在编译期间对注解进行处理,我们可以把它看做是一组编译器的插件,在这些插件里面,可以读取、修改、添加抽象语法树中的任意元素。如果这些插件在处理注解期间对语法树进行了修改,编译器将回到解析及填充符号表的过程重新处理,直到所有插入式注解处理器都没有再对语法树进行修改为止,每一次循环称为一个Round,也就是上面绿色图中回环的过程。

在Javac源码中,插入式注解处理器的初始化过程是在initProcessAnnotations()方法中完成的,而它的执行过程则是在processAnnotations()方法中完成的,这个方法判断是否还有新的注解处理器需要执行,如果有的话,通过com.sun.tools.javac.processing.JavacProcessingEnvironment类的doProcessing()方法生成一个新的JavaCompiler对象对编译的后续步骤进行处理。

4.语义分析与字节码生成

语法树能表示一个结构正确的源程序的抽象,但无法保证源代码是符号逻辑的。而语义分析的主要任务是对结构上正确的源程序进行上下文有关性质的审查,如进行类型审查。

语义分析包括两个步骤:

1)标注检查(attribute()方法)

2)数据及控制流分析(flow()方法)

4.1标记检查

检查的内容包括:变量使用前是否已被声明、变量与赋值之间的数据类型是否能够匹配等。

常量折叠:

int a = 1+2;这个插入式表达式(Infix Expression)的值已经在语法树上标注出来了(ConstantExpressValue:3)。

由于编译期间进行了常量折叠,所以在代码里面定义“a=1+2”比起直接定义“a=3”,并不会增加程序运行期哪怕仅仅一个CPU指令的运算量。

标注检查步骤在Javac源码中的实现类是com.sun.tools.javac.comp.Attr类和com.sun.tools.javac.comp.Check类。

4.2数据及控制流分析

数据及控制流分析是对程序上下文逻辑更进一步的验证,它可以检查:程序局部变量在使用前是否有赋值、方法的每条路径是否都有返回值、是否所有的受查异常都被正确处理了等。

编译时期的数据及控制流分析与类加载时的数据及控制流分析的目的基本上是一致的,但校验范围有所区别,有一些校验项只有在编译期或运行期才能进行。

局部变量与字段(实例变量、类变量)是有区别的,它在常量池中没有CONSTANT_Fieldref_info的符号引用,自然就没有访问标志(Access_Flags)的信息,甚至可能连名称都不会保留下来(取决于编译时的选项),自然在Class文件中不可能知道一个局部变量是不是声明为final了。因此,将局部变量声明为final,对运行期是没有影响的,变量的不变性仅仅由编译器在编译期间保障。

在Javac的源码中,数据及控制流分析的入口是flow()方法,具体操作由com.sun.tools.javac.comp.Flow类完成。

4.3解语法糖

语法糖(Syntactic Sugar),也称糖衣语法。指在计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。

Java中最常用的语法糖:泛型、变长参数、自动装箱/拆箱等,虚拟机运行时不支持这些语法,它们在编译阶段还原回简单的基础语法结构,这个过程称为解语法糖。

在Javac的源码中,解语法糖的过程由desugar()方法触发,在com.sun.tools.javac.comp.TransTypes类和com.sun.tools.javac.comp.Lower类中完成。

4.4字节码生成

字节码生成是Javac编译过程的最后一个阶段,在Javac源码里面由com.sun.tools.javac.jvm.Gen类来完成。

字节码生成阶段不仅仅是把前面各个步骤所生成的信息(语法树、符号表)转化成字节码写到磁盘中,编译器还进行了少量的代码添加和转换工作。

实例构造器()方法和类构造器()方法就是在这个阶段添加到语法树之中的(注意,这里的实例构造器并不是指默认构造函数,如果用户代码中没有提供任何构造函数,那编译器将会添加一个没有参数的、访问性(public、protected、private)与当前类一致的默认构造函数,这个工作在填充符号表阶段就已经完成),这两个构造器的产生过程实际上是一个代码收敛的过程,编译器会把语句块(对于实例构造器而言是“{}”块,对于类构造器而言是“static{}”块)、变量初始化(实例变量和类变量)、调用父类的实例构造器(仅仅是实例构造器,()方法中无须调用父类的()方法,虚拟机会自动保证父类构造器的执行,但在()方法中经常会生成调用java.lang.Object的()方法的代码)等操作收敛到()和方法之中,并且保证一定是按先执行父类的实例构造器,然后初始化变量,最后执行语句块的顺序进行,上面所述的动作由Gen.normalizeDefs()方法来实现。

除了生成构造器以外,还有其它的一些代码替换工作用于优化程序的实现逻辑,如把字符串的加操作替换为StringBuffer或StringBuilder的append()操作等。

完成了对语法树的遍历和调整之后,就会把填充了所有所需信息的符号表交给com.tools.javac.jvm.ClassWriter类,由这个类的writeClass()方法输出字节码,生成最终的Class文件,到此为止整个编译过程宣告结束。

编译过程

javac 的编译过程可以分为 1 个准备过程和 3 个处理过程:

1 个准备过程: ◉ 准备过程:初始化插入式注解处理器 3 个处理过程: ◉ 解析与填充符号表 ◉ 注解处理(插入式注解器) ◉ 分析与字节码生成

javac 的编译过程,如下图所示:

在上述的编译过程中,在 “处理注解” 的阶段可能会产生新的符号,那么就必须再执行一次 “解析与填充符号表” 处理新的符号。

分析源码,javac 编译代码是 com.sun.tools.javac.main.JavaComplier 类的 compile() 和 compile2() 方法。

compile() 方法的部分代码:

// 1 初始化插入式注解处理器initProcessAnnotations(processors);
    // These method calls must be chained to avoid memory leaksdelegateCompiler =    
    // 3 注解处理    processAnnotations(            
    // 2.2 解析与填充符号表--填充符号表            enterTrees(stopIfError(CompileState.PARSE,         // 2.1 解析与填充符号表--词法分析、语法分析            parseFiles(sourceFileObjects))),    classnames);
    // 4 分析与字节码生成delegateCompiler.compile2();delegateCompiler.close();elapsed_msec = delegateCompiler.elapsed_msec;

compile2() 方法的部分代码:

case BY_TODO:    while (!todo.isEmpty())        
    // 4.4 分析与字节码生成--生成字节码        generate(            
    // 4.3 分析与字节码生成--解语法糖            desugar(               
    // 4.2 分析与字节码生成--数据流分析                flow(                   
    // 4.1 分析与字节码生成--标注                    attribute(todo.remove()))));    break;

解析与填充符号表

解析与填充符号表分为两个过程:词法、语法分析 和 填充符号表。

具体流程为: 生成 Token 集合 --> 生成抽象语法树 --> 填充符号表

  • 词法、语法分析

词法、语法分析 parseFiles() 方法的源码,如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IVUS8xxx-1672301470200)(null)]

词法分析就是将源代码的字符流转变成标记(Token)集合的过程,标记是编译时的最小元素。

词法分析的过程由 com.sun.tools.javac.parser.Scanner 类实现,逐个读取源代码的单个字符,通过 nextToken() 方法构造每一个 Token。

关键字、变量名、字面量、运算符都可以作为标记。

例如,下面这段代码,包含了 6 个标记,分别是:int、a、=、b、+、2。

int a = b + 2;

转变成标记(Token)集合的结果,如下图所示:

语法分析就是根据 标记(Token)集合 构造成 抽象语法树 的过程,抽象语法树(AST)的每一个节点代表着程序代码的一个语法结构。

IntelliJ IDEA 可以安装 JDT AstView 插件,可视化源代码的抽象语法树。

JDT AstView https://plugins.jetbrains.com/plugin/9345-jdt-astview

上面代码:int a = b + 2; 的抽象语法树的结构视图,如下图所示:

语法分析过程由 com.sun.tools.javac.parser.Parser 类实现,抽象语法树由 com.sun.tools.javac.tree.JCTree 类表示。

经过词法、语法分析生成抽象语法树之后,编译器就不会再对源码字符流进行操作,后续的操作都是建立在抽象语法树之上。

  • 填充符号表

完成词法、语法分析之后,下一个阶段就是填充符号表。

填充符号表 enterTrees() 方法的源码,如下图所示:

符号表(Symbol Table)是由一组符号地址和符号信息构成的数据结构,可以类比想象成哈希表中键值对的存储形式。

符号表所登记的信息在编译的不同阶段都会被用到,在目标代码生成阶段,对符号名进行地址分配时,符号表是地址分配的直接依据。

填充符号表的过程由 com.sun.tools.javac.comp.Enter 类实现,将会生成一个待处理列表,其中包含了每一个编译单元的抽象语法树的顶级节点,以及 package-info.java(如果存在)的顶级节点。

package-info.java:为包级文档和包级别注释

注解处理

注解处理 processAnnotations() 方法的源码,如下图所示:

插入式注解处理器是在编译器对代码中的特定注解进行处理,在前端编译器的工作过程中,对抽象语法树中的任意元素进行读取、修改、添加。

插入式注解处理器在处理注解的过程中,如果对语法树进行过修改,那么编译器将会重新到解析与填充符号表的阶段重新处理,直到没有对语法树进行修改为止,每一次循环过程称为一次轮次(Round)。

通过 com.sun.tools.javac.processing.JavacProcessingEnvironment 类的 domProcessing() 方法来执行语法树中的插入式注解处理器,生成新的 JavaCompiler 对象。

分析与字节码生成

经过词法、语法分析和注解处理两个过程,得到的抽象语法树可以表示一个结构正确的源程序,但是无法保证语义是否符合逻辑。

因此,需要对抽象语法树进行语义分析,分为两个步骤:标注检查、数据及控制流分析。

分析与字节码生成的 comile2() 方法的源码,如下图所示:

  • 标注检查

标注检查主要是检查变量使用前是否已被声明、变量与赋值之间的数据类型是否能够匹配等等。

例如,下面这段代码:

// 检查变量与赋值之间的数据类型是否匹配(通过)int a = 1;boolean b = true;char c = 2;
    // 检查变量与赋值之间的数据类型是否匹配(不通过)int d = a + b;char e = a + c;
    // 检查变量使用前是否已被声明(不通过)int f = a + g;

除此之外,标注检查还会进行一个称为常量折叠(Constant Folding)的代码优化。

例如,下面这段代码:

// 优化前int a = 1 + 2;// 优化后int a = 3;

标注检查的入口是 attribute() 方法,具体操作由如下两个类实现。

◉ com.sun.tools.javac.comp.Attr ◉ com.sun.tools.javac.comp.Check

  • 数据及控制流分析

数据及控制流分析是对程序上下文逻辑更进一步的验证,检查出局部变量使用前是否已经赋值、方法是否有返回值、所有受查异常是否被正确处理、final 变量只能赋一次值等等。

例如,下面这段代码:

// 方法一:public void foo() {    final int a = 0;    a = 1;}

使用 javac 编译会报错,如下图所示:

这里拓展一下,final 关键字修饰的局部变量只能在编译期进行检查,不能在运行期检查。

因为局部变量表在常量池中没有 CONSTANT_Fieldref_info 的符号引用,并不能存储访问标志(access_flags),所以在 .class 文件中是不可能知道一个局部变量是否被声明为 final。

数据及控制流分析的入口是 flow() 方法,具体操作由如下一个类实现。

◉ com.sun.tools.javac.comp.Flow

  • 解语法糖

语法糖(Syntactic Sugar),也称为糖衣语法,指的是在计算机语言中添加某种语法,这种语法对语言的编译结果和功能并没有实际影响,但是却能更方便程序员使用该语言。

语法糖的作用是:减少代码量、增加程序的可读性。

Java 中最常见的语法糖:泛型、边长参数、自动装箱拆箱……

解语法糖的过程就是将语法糖还原回原始的基础语法结构。

解语法糖的入口是 desugar() 方法,具体操作由如下一个类实现。

◉ com.sun.tools.javac.comp.Lower

  • 字节码生成

字节码生成阶段不仅把前面各个步骤生成的语法树和符号表转化成字节码指令写到磁盘中,生成 .class文件,还进行少量的代码添加和转换工作。

例如,实例构造器 () 和类构造器 () 就是再字节码生成阶段添加到语法树之中。

除此之外,还有一些代码替换工作用于优化程序某些逻辑的实现方式,也是在字节码生成阶段进行。例如:把字符串的加操作替换成 StringBuffer 或 StringBuilder 的 append()。

字节码生成的入口是 generate() 方法,具体操作由如下两个类实现。

◉ com.sun.tools.javac.jvm.Gen (对语法树进行调整,进行少量的代码添加和转换工作) ◉ com.sun.tools.javac.jvm.ClassWriter (将语法树、符号表转化成字节码,写入到磁盘)

以上是Javac编译过程以及简单的解析;Android开发中许多这个的技术知识需要我们一个个去掌握,而网上的资料比较杂乱;找起来比较繁琐,这里推荐这个文档《Android核心技术手册》里面有30多个技术模块可以参考学习。

编译HelloWorld

新建一个

HelloJavac.java

可以看到HelloJavac目前是还没有编译的。

配置参数:

这里我配置的参数是:-d M o d u l e F i l e D i r ModuleFileDir ModuleFileDir\target\classes M o d u l e F i l e D i r ModuleFileDir ModuleFileDir\src\main\java\com\hello\HelloJavac.java 读者根据自己的路径自行更改,宏是从图红框里选的。

编译结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F8IU9U3n-1672301470246)(null)]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/126953.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

测试工程师正遭「革命」 AI将改写测试模式

文章目录❤️‍🔥 软件测试的现状❣️ 功能测试的短板❣️ 过于的依赖工具❤️‍🔥 测试行业的两极分化❤️‍🔥 纯功能测试人员应该如何破局❣️ 龙测 AI TestOps 云平台❣️ AI TestOps 亮相 TICA❣️ AI TestOps 所实现的混合模型解决方案…

相关系数(皮尔逊pearson相关系数和斯皮尔曼spearman等级相关系数)

目录 总体皮尔逊Person相关系数: 样本皮尔逊Person相关系数: 两点总结: 假设检验:(可结合概率论课本假设检验部分) 皮尔逊相关系数假设检验: 更好的方法:p值判断方法 皮尔逊相…

lua调用c动态库实例

简介 Lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。 特点 轻量级: 它用标准C语言编写并以源代码形式开放,编译后仅仅一百余K&a…

STM32/51单片机实训day4——RFID数据读取|RC522|串口数据收发、可模拟RFID (三) 仿真

目录 1 任务指导 2 实验步骤 3 串口调试 4 USART配置 5 fputs函数重写 内 容:能够读取RFID卡S50的ID——编程实现串口数据收发 学 时:3学时 知识点:电路图设计、USART配置 重点: USART配置 难点:USART配置 时…

赶快升级吧!PHP8比PHP5快41倍,比PHP7快3倍

本文得出的结论,归结于仅运行纯CPU任务的脚本的基准测试结果,不需要I/O操作的任务,例如访问文件、网络或数据库连接。 这些是纯 CPU 基准测试。它们并未涵盖 PHP 性能的所有方面,并且它们可能无法代表实际情况。然而,结…

用Python标准库统计CSDN阅读量

urllib基础 一般做爬虫其实很少有推荐urllib的,但urllib乃是Python标准库成员,在要求比较简单的情况下,采用urllib还是比较方便的。 作为爬虫入门必学包,urllib最常用的函数一定是urllib.request中的urlopen。其返回对象是HTTPR…

ES学习路程(二)

关于ES第一篇是在Linux安装,为了方便我在windows搭建一套ES和kibana版本(7.15.0) 第一步:下载安装ES在windows 官网下载相应版本的es和kibana: https://www.elastic.co/cn/downloads/past-releases/elasticsearch-7…

《图解TCP/IP》阅读笔记(第八章 8.6、8.7)—— 网络管理与其他应用层协议

前言 本篇是第八章的最后一篇 8.6 网络管理 从前,网络管理凭借管理员的记忆和直觉进行,但是网络规模越大,人的力量就越不足,所以需要一个严密的管理工具或方法。 在TCP/IP的网络管理中,可以使用SNMP(Si…

uni-app——小程序实现本地图片的上传以及身份证的智能识别

文章目录 前言一、示例图二、实现过程 1.完成提交图片的api地址2.获取本地图片3.将本地图片上传至开发者服务器三、具体实现代码四、身份证的智能识别总结前言 上传本地图片的功能很常见,那么具体该如何实现呢? 一、示例图 二、实现过程 1.完成提交图…

操作系统接口系统调用的实现

接口 连接两个东西,信号转换,屏蔽细节… 操作系统接口 连接上层用户和操作系统软件,方便了使用,屏蔽了细节。 操作系统接口的形式 为应用层提供一些重要的函数,如printf,write,read等。接口…

Windows Active Directory —— 常见的远程控制对比

在windows环境中,需要远程访问的时候很多,使用的工具和命令也各式各样,我把自己常用的命令和工具总结一下 远程访问方式: 1)对服务器而言,RDP这个绝对是最常见的方式,mstsc /v:remoteserver 即可打开 2)winrs和winrm,这个可以允许我们通过命令行来远程访问,远程服务…

TypeScript基础类型

目录 数字 number 字符串 string 布尔 boolean 数组 Array 元组 枚举 enum 任意值 any void Null 和 Undefined null undefined Never 数字 number let age: number 24; 虽然爆红,但是依然能改 字符串 string let name:string"张三" 布尔 bo…

ef参数设置说明(faiss)

1、模型参数:1000代表聚类中心个数 随着聚类个数的增加,模型索引的构建时间近似指数增加但搜索精度也线性增加,不影响内存占用,几乎不影响搜索耗时 结论:在 Faiss 引擎的聚类情况下,对于百万级别的数据大概…

B/S结构和C/S结构详细介绍

什么是c/s结构、b/s结构 1、C/S结构,即Client/Server(客户机/服务器)结构,是大家熟知的软件系统体系结构,通过将任务合理分配到Client端和Server端,降低了系统的通讯开销,充分利用两端硬件环境的优势。早期的软件系统…

Haploview做单倍型教程1--软件安装

大家好,我是邓飞,这里介绍一下如何使用Haploview进行单倍型的分析。 计划分为三篇文章: 第一篇:Haploview做单倍型教程1–软件安装第二篇:Haploview做单倍型教程2-分析教程第三篇:Haploview做单倍型教程3…

HCIA(1)

一. 计算机网络的诞生及发展 1946年2.14日,美国宾夕法尼亚大学为了美国军方用于导弹计算,发明了世界上第一台计算机,而计算机改变并且引领了世界的发展。 计算机是现代一种用于高速计算的电子计算机器,可以进行数值计算&#x…

『C语言』字符串的输入gets()和输出puts()

🚩write in front🚩 🔎大家好,我是謓泽,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流🔎 🏅2021年度博客之星物联网与嵌入式开发TOP5&#xff5…

MATLAB-fplot绘图函数

前面介绍的plot函数是将外部输人或者通过函数数值计算得到的数据矩阵转化为二维图形。在实际的应用中,用户可能并不知道所要绘制的二维图形中函数随着变量变化的趋势,假如此时用plot函数来绘制图形,则可能会由于变量的取值间隔不合理而导致所绘制的二维图…

2022年广西最新建筑施工焊工(建筑特种作业)模拟试题及答案

百分百题库提供特种工(焊工)考试试题、特种工(焊工)考试预测题、特种工(焊工)考试真题、特种工(焊工)证考试题库等,提供在线做题刷题,在线模拟考试,助你考试轻…

C++【跳表】

文章目录一、什么是跳表二、跳表的实现三、跳表性能分析一、什么是跳表 skiplist本质上也是一种查找结构,用于解决算法中的查找问题,跟平衡搜索树和哈希表的价值是一样的,可以作为key或者key/value的查找模型。 skiplist是由William Pugh发…