编译实际就是翻译,是将人类易读(为啥?因为开发语言的目的就是为了让人容易使用)的语言转换为机器或程序易读的语言。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. Kettle下载和安装

    一.Kettle简介 Kettle 是 PDI 以前的名称,PDI 的全称是Pentaho Data Integeration,Kettle 本意是水壶的意思,表达了数据流的含义.Kettle是一款国 ...

  2. Git:国内用命令行访问GitHub的方法

    1 直接改Hosts文件(现在不太管用了) 如果你是Linux或Mac系统,那么可以通过命令sudo vim /etc/hosts打开Hosts文件,并加入以下内容: 140.82.114.25 al ...

  3. web component基础概念及使用

    概念和使用 作为开发者,我们都知道尽可能多的重用代码是一个好主意.这对于自定义标记结构来说通常不是那么容易 - 想想复杂的HTML(以及相关的样式和脚本),有时您不得不写代码来呈现自定义UI控件,并且 ...

  4. Python ipset iptables 实现蜜罐 自动封堵扫描者IP

    Python ipset iptables 实现蜜罐 自动封堵扫描者IP 蜜罐可以诱捕入侵者,但无法实时封堵入侵者,必须在事后通过日志进行手工封堵. 有没有什么办法可以实现自动封堵入侵者IP? ipt ...

  5. Django——messages消息框架

    在网页应用中,我们经常需要在处理完表单或其它类型的用户输入后,显示一个通知信息给用户.对于这个需求,Django提供了基于Cookie或者会话的消息框架messages,无论是匿名用户还是认证的用户. ...

  6. WPF使用WPFMediaKit/AForge调用摄像头示例 .net core 8.0 也支持

    调用摄像头使我们经常会用到的一个功能,可以根据摄像头捕捉到的图像进行分析处理来做很多的东西,比如电子档案.图像识别.AI分析等等. 本示例中主要介绍Nuget最常用的两个调用摄像头的轮子 WPFMed ...

  7. docker部署php8.0 nginx1.18 mysql5.7 dnmp环境

    php8.0 nginx1.18 mysql5.7 #安装docker wget -O /etc/yum.repos.d/ali_docker-ce.repo https://mirrors.aliy ...

  8. U-Boot命令使用

    帮助命令 help 所有命令提示: h ? help 某一条命令提示: ? 命令名 help 命令名 信息查询命令 bdinfo 查看板子信息: => bdinfo arch_number = ...

  9. java小记-scanner

    不想打字也是我的罪过吗? 作业2: 老师的代码: 结果 我的代码看起来冗余: 想说的: 我的本意是以为scanner只能记录一个数,然后就想着输入两次就能算两个数了,但没想到人家只是让你输就完了.不要 ...

  10. 阻塞外挂 TCP 端口 让外挂服务器增加无用处理 反攻击 是4个IP 苹果 安卓 pc 域名

    using namespace std;#include<stdlib.h>#pragma comment(lib,"WS2_32.lib") #include < ...