编译实际就是翻译,是将人类易读(为啥?因为开发语言的目的就是为了让人容易使用)的语言转换为机器或程序易读的语言。Java的编译器是javac,它将.java文件编译为.class文件,也就字节码文件。

和中级语言如C不同的是,Java没有直接生成CPU可读的机器码。为了实现跨平台能力,javac生成的字节码会由不同平台的虚拟机来识别。编译的过程大学本科会学到,在课程《编译原理》中;硕士阶段会继续深入学习形式语言和自动机理论。编译原理的课件网上一大堆,这里不说了。

放一张Java编译过程的图(出处见水印,侵删):

这个只是从编译原理角度看到的Java编译过程。几乎全部语言都是这样的编译过程,但是具体到某一门语言,都会有各自根据语言特性的差异实现。比如Java有注解处理程序,最常见的就是lombok。

上图是OpenJDK官方给出的编译概图。可以看到分为三个阶段:

  1. javac会把命令指定的源文件解析为语法树,并把外部可见的定义放到符号表中
  2. 调用注解处理程序,如果生成了新的文件就重新走第一步,一直到没有新文件产生
  3. 把语法树解析为类文件。类引入的其他依赖会在类路径寻找并被编译

这三个阶段是由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文件的编译的更多相关文章

  1. 在windows下使用cmd命令行对java文件进行编译和执行

    windows下利用cmd命令行可以调用jdk里的javac.exe和java.exe对java文件进行编译和执行,前提是jdk已成功安装并正确配置相关环境变量 相关配置链接:java基础学习总结—— ...

  2. Java文件手动编译执行步骤

    Java编译执行步骤: 1)将 Java 代码编写到扩展名为 .java 的文件中.2)通过 javac 命令对该 java 文件进行编译.3)通过 java 命令对生成的 class 文件进行运行. ...

  3. MyEclipse2014,java文件无法编译,run as上是none applicable,不是文件本身的问题

    1.配置一下JDK目录 2.window -> Preferences -> java -> Installed JREs -> Add

  4. javaweb项目部署到tomcat之后java文件没有编译

    1.选中你的项目==>选择Project 2.将Build Automatcally前的对号去掉后再Clean一下你的项目 这样就可以了,

  5. 多个java文件编译并打成jar包经典方法

    首先,多个java文件的编译 find . -type f -name *.java > compilelist (.代表当前路径) javac -cp "$CLASSPATH&quo ...

  6. JAVA 文件编译执行与虚拟机(JVM)简单介绍

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcytpo3 java程序的内存分配 JAVA 文件编译执行与虚拟机(JVM)介绍 ...

  7. 在dos中编译java文件

    首先Dos中 编译java文件是:javac (所有)类名.java 运行java文件是:java 包名.类名 java指令默认在寻找class文件的地址是通过CLASSPATH环境变量中指定的目录中 ...

  8. jdk编译java文件时出现:编码GBK的不可映射字符

    出现此问题的几种解决办法: 1.cmd下使用javac编译java文件 如: javac test.java 解决办法:编译时加上encoding选项 javac -encoding UTF-8 te ...

  9. 【转】 Apk文件及其编译过程

    Apk文件概述 Android系统中的应用程序安装包都是以apk为后缀名,其实apk是Android Package的缩写,即android安装包. 注:apk包文件其实就是标准的zip文件,可以直接 ...

  10. Linux巩固记录(2) java项目的编译和执行

    由于要近期使用hadoop等进行相关任务执行,操作linux时候就多了 以前只在linux上配置J2EE项目执行环境,无非配置下jdk,部署tomcat,再通过docker或者jenkins自动部署上 ...

随机推荐

  1. 04 Xpath_[实例]爬取maoyan

    目录 Xpath lxml库的安装和使用 提取的内容 代码 生成的csv 下载的图片 参考文档 Xpath lxml库的安装和使用 提取的内容 随意选取的一段 节点包含的影片信息,如下所示: < ...

  2. docker-compose 安装 mysql:5.7.31

    目录 一.新建一个启动服务的目录 二.新建文件docker-compose.yml 三.新建角本文件 init-mysql.sh 四.实使化目录和配置文件 启动服务 登陆mysql 其它操作 参考文档 ...

  3. Java变量自增表达式 i = i++ 的底层逻辑(简述)

    Java变量自增表达式 i = i++ 的底层逻辑(简述) 前言 很多老师告诉我们,i = i++ 的运算过程是 temp = i; i ++; i = temp; 所以i的值不变.但我总觉得这个te ...

  4. js中关于return和if条件处理

    好玩的 // if (true) { // return // } // // 不会打印 // console.log('1') // if (false) { // return // } // / ...

  5. ctfshow_web_1(困难题)

    CTFshow web1(困难题) 根据前面做题经验,看见登录框基本都是跑一下爆破,弱口令等等 这里用 dirmap 目录爆破爆出来有一个 www.zip 把他下载下来 看了 login.php 和 ...

  6. C# 程序集、模块和类型概念及关系

    目录 C# 程序集.模块和类型概念及关系 概述 程序集 模块 类型 程序集.模块和类型的关系 总结 引用 C# 程序集.模块和类型概念及关系 概述 在 C# 中,程序集.模块和类型是构成 .NET 应 ...

  7. k8s其它学习链接

    k8s弹性伸缩概念以及测试用例 https://www.cnblogs.com/jasonboren/p/11493347.html CKA看这一篇就够了 k8s官网 k8s基础之六 有状态和无状态的 ...

  8. RestTemplate 介绍和用法

    RestTemplate 简介 RestTemplate 是从 Spring3.0 开始支持的一个 HTTP 请求工具,它提供了常见的REST请求方案的模版,例如 GET 请求.POST 请求.PUT ...

  9. 公众号5月C#/.NET热文一览

    C#/.NET/.NET Core全面的学习.工作.面试指南知识库 转眼之间维护DotNetGuide(C#/.NET/.NET Core学习.工作.面试指南知识库)已经持续超过了三年多的时间,Com ...

  10. Flutter(四):Flutter 语言 Dart基础使用

    一.变量和常量 变量 var a = 1; var str = 'abc'; 常量 const PI = 3.14; 不可变对象 final PI = 3.14; final TIME = new D ...