JAVA文件的编译
编译实际就是翻译,是将人类易读(为啥?因为开发语言的目的就是为了让人容易使用)的语言转换为机器或程序易读的语言。Java的编译器是javac,它将.java文件编译为.class文件,也就字节码文件。
和中级语言如C不同的是,Java没有直接生成CPU可读的机器码。为了实现跨平台能力,javac生成的字节码会由不同平台的虚拟机来识别。编译的过程大学本科会学到,在课程《编译原理》中;硕士阶段会继续深入学习形式语言和自动机理论。编译原理的课件网上一大堆,这里不说了。
放一张Java编译过程的图(出处见水印,侵删):
这个只是从编译原理角度看到的Java编译过程。几乎全部语言都是这样的编译过程,但是具体到某一门语言,都会有各自根据语言特性的差异实现。比如Java有注解处理程序,最常见的就是lombok。
上图是OpenJDK官方给出的编译概图。可以看到分为三个阶段:
- javac会把命令指定的源文件解析为语法树,并把外部可见的定义放到符号表中
- 调用注解处理程序,如果生成了新的文件就重新走第一步,一直到没有新文件产生
- 把语法树解析为类文件。类引入的其他依赖会在类路径寻找并被编译
这三个阶段是由JavaCompiler类控制的,我们来分别看一下这三个过程。
com.sun.tools.javac.main.JavaCompiler类,位于tools.jar中。tools.jar提供了Javac/javap/javadoc等命令的实现
解析和进入
解析
源文件会被处理为unicode字符(因为源文件的字符是任意的,可以是uft-8也可以是gbk,也可以是其他任意),在进入虚拟机以前使用的是优化过的utf-8字符,进入虚拟机后是utf-16。然后通过com.sun.tools.javac.parser.Scanner类转换为token。com.sun.tools.javac.parser.JavacParser会读取token流,并调用com.sun.tools.javac.tree.TreeMaker创建语法树。语法树是从com.sun.tools.javac.tree.JCTree的子类构造的,JCTree是一个抽象类,并且是接口Tree的实现类。
进入
每一棵语法树都会被com.sun.tools.javac.comp.Enter处理,它会将遇到的符号都放入符号表。符号表是编译过程中用到的一张hash表,它会记录变量、方法、自定义类等的定义信息,可用于检查代码是否正确(比如变量是否重复定义,因为重复定义会在符号表中冲突),使用符号的时候就直接从符号表拿,并按照符号表中记录的定义使用。语法树生成阶段的产出是一个TODO list,里面是需要分析并生成类文件的语法树。创建语法树包括3个阶段,类排队进入下一个阶段。
在第一阶段,所有类符号都被放入各自的可访问范围(public/private/包/子类),并递归判断引用类的可访问性。完成的时候会给一个com.sun.tools.javac.comp.MemberEnter对象。
如果存在package-info.java,它的语法树也会放进TODO list
在第二阶段,通过调用MemberEnter.complete()方法标记类被处理完。完成包括两步:(1)可以看到类的参数、子类、接口等信息;(2)进入自己的可访问范围。(2)依赖(1),所以(1)后面进入了一个halfcompleted队列。
第三阶段是在符号表都生成以后,符号的注解都会被验证是否合法(比如@Override)。
第一阶段决定了一个定义能不能被访问到,第二阶段是Lazy的,类的成员第一次被访问才会进入符号表,但是最终队列里的类都会被处理掉变成完成。这是由MemberEnter的两阶段来保证的。
注解处理
这里用到com.sun.tools.javac.processing.JavacProcessingEnvironment。
从概念上看,注解处理是编译前的一个小步骤,主要就是解析源文件,看一下需要调用哪个注解处理器。第一轮之后如果产生了新文件,就多执行几轮。直到没有新文件了就开始编译。
而实际上,在文件被编译以前,可能不知道该用哪个注解处理器。所以为了避免使用注解处理器前进行不必要的解析并放进符号表,JavacProcessingEnvironment有点没按照概念模型走,但是依然满足在实际编译前就处理了注解。怎么办的呢?
在文件被解析并进入符号表后,JavacProcessingEnvironment会被调用。一般来说,如果符号有误就会中止处理并报错,但是有可能符号定义是通过注解产生的(比如lombok的getter/setter/ToString),这时候不能报错。
所以要处理注解,要使用单独的类加载器。
注解处理器运行后,JavacProcessingEnvironment会决定是否需要进一步处理。需要继续处理注解的话会创建一个新的JavaCompiler对象,从头开始处理新文件。如此反复。最后JavacProcessingEnvironment会返回一个JavaCompiler对象提醒开始编译,这个JavaCompiler对象要么是最初用于解析源文件那个,要么是最后一轮注解处理中用到那个。
分析和生成
这一步是在前面的基础上生成.class文件的。
分析语法树的时候如果发现依赖了但是没在javac命令中指定的类,就去源文件路径和类路径下查找。如果在类路径下找到,就直接拿过来看看是否是合法使用;如果在源文件路径下找到,就要进行解析、放入符号表、进入TODO list。
语法树的分析和类文件生成是由一些处理TODO list条目的“观察者”完成的。这里并不要求观察者们一个一个的去处理,因为是在内存中完全不必要。只要最终每个条目都被处理即可(除非出错了没法处理)处理过程会用到这些类:
- com.sun.tools.javac.comp.Attr
- com.sun.tools.javac.comp.Flow
- com.sun.tools.javac.comp.TransTypes
- com.sun.tools.javac.comp.Lower
- com.sun.tools.javac.jvm.Gen
一旦类文件生成,前面生成的东西就不用了。为了节省内存空间会把他们的引用改成null,以让GC去回收掉。
JAVA文件的编译的更多相关文章
- 在windows下使用cmd命令行对java文件进行编译和执行
windows下利用cmd命令行可以调用jdk里的javac.exe和java.exe对java文件进行编译和执行,前提是jdk已成功安装并正确配置相关环境变量 相关配置链接:java基础学习总结—— ...
- Java文件手动编译执行步骤
Java编译执行步骤: 1)将 Java 代码编写到扩展名为 .java 的文件中.2)通过 javac 命令对该 java 文件进行编译.3)通过 java 命令对生成的 class 文件进行运行. ...
- MyEclipse2014,java文件无法编译,run as上是none applicable,不是文件本身的问题
1.配置一下JDK目录 2.window -> Preferences -> java -> Installed JREs -> Add
- javaweb项目部署到tomcat之后java文件没有编译
1.选中你的项目==>选择Project 2.将Build Automatcally前的对号去掉后再Clean一下你的项目 这样就可以了,
- 多个java文件编译并打成jar包经典方法
首先,多个java文件的编译 find . -type f -name *.java > compilelist (.代表当前路径) javac -cp "$CLASSPATH&quo ...
- JAVA 文件编译执行与虚拟机(JVM)简单介绍
详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcytpo3 java程序的内存分配 JAVA 文件编译执行与虚拟机(JVM)介绍 ...
- 在dos中编译java文件
首先Dos中 编译java文件是:javac (所有)类名.java 运行java文件是:java 包名.类名 java指令默认在寻找class文件的地址是通过CLASSPATH环境变量中指定的目录中 ...
- jdk编译java文件时出现:编码GBK的不可映射字符
出现此问题的几种解决办法: 1.cmd下使用javac编译java文件 如: javac test.java 解决办法:编译时加上encoding选项 javac -encoding UTF-8 te ...
- 【转】 Apk文件及其编译过程
Apk文件概述 Android系统中的应用程序安装包都是以apk为后缀名,其实apk是Android Package的缩写,即android安装包. 注:apk包文件其实就是标准的zip文件,可以直接 ...
- Linux巩固记录(2) java项目的编译和执行
由于要近期使用hadoop等进行相关任务执行,操作linux时候就多了 以前只在linux上配置J2EE项目执行环境,无非配置下jdk,部署tomcat,再通过docker或者jenkins自动部署上 ...
随机推荐
- 网络协议分析与抓包 TCP/IP UDP等
学习地址: https://www.bilibili.com/video/BV1hV411U74y?p=4 https://www.bilibili.com/video/BV1S7411R7kF?p= ...
- gin-vue-admin开发教程 01安装与启用
目录 目标 视频教程地址: 环境要求 前端环境安装文档: 安装node npm cnpm yarn(选装) 后端环境安装文档: Golang1.14.2 环境的安装 goland的配置 gin-vue ...
- Linux中的touch命令
Linux中一个文件有3种时间属性,分别是mtime,ctime,atime: modification time (mtime) 当该文件的『内容数据』变更时,就会升级这个时间!内容数据指的是文件的 ...
- 案例-java贪吃蛇(附源码)
创建屏幕 开始游戏的窗口,首先引入窗口,然后在窗口画布上进行添加各类动画. JFrame frame=new JFrame("My SnakeGame"); Jframe 是个类, ...
- OpenAirInterface,开源的 4G EPS 实现
目录 文章目录 目录 前文列表 OSA OpenAirInterface OAI 的仿真 物理信道仿真 系统级仿真 OAI 的 SDR LTE 参考文档 前文列表 <USRP B210 软件定义 ...
- RTMP 直播 H265 推流适配总结
1.在iOS11的系统之上,苹果逐渐放开H265硬编硬解的能力,硬解码的能力只要升级到iOS11之后,iPhone6+以上的机型就支持了(印象中): H265硬编码的能力对设备要求较高,不仅要求系统版 ...
- 关于ICMP隧道一点理解(起于修改wien-qq的记住密码)
起 使用linux半年多以来,一直有一个我很需要但我无法完美解决的东西困扰这我-----(linux QQ) 目前我的解决方案是GitHub上的一个第三方QQ(有关ICMP隧道的搭建见 承,转) 但是 ...
- TypeScript keyof
keyof 是 TypeScript 中的一个关键字,用于获取一个类型的所有键(属性名)构成的联合类型.它主要用于在类型系统中引用对象类型的键. 以下是一些 keyof 的用法和示例: 1. 获取对象 ...
- while适用于不确定循环次数
// 当前有一个随机数,是生成100-999的随机数值 // 需要生成数值666,需要知道循环了多少次,才生成的666这个数值 // 我们可以通过循环来实现 ...
- CF437E The Child and Polygon
The Child and Polygon 题解 这世界这么大,遇到了这个奇奇怪怪的题. 这道题其实可以很自然的联想到卡特兰数. 在卡特兰数的计数中,有这么一个意义:\(C_n\) 表示把有 \(n+ ...