关键词:
java源代码(符合语言规范)-->javac-->.class(二进制文件)-->jvm-->机器语言(不同平台不同种类)
如何让java的语法规则适应java虚拟机的语法规则?这个任务由javac编译器来完成java语言规范转换成java虚拟机语言规范。
编译流程:
流程:
- 词法分析器:将源码转换为Token流
- 将源代码划分成一个个Token(找出java语言中的if,else,for等关键字)
- 语法分析器:将Token流转化为语法树
- 将上述的一个个Token组成一句句话(或者说成一句句代码块),检查这一句句话是不是符合Java语言规范(如if后面跟的是不是布尔判断表达式)
- 语义分析器:将语法树转化为注解语法树
- 将复杂的语法转化成简单的语法(eg.注解、foreach转化为for循环、去掉永不会用到的代码块)并做一些检查,添加一些代码(默认构造器)
- 代码生成器:将注解语法树转化为字节码(即将一个数据结构转化成另一个数据结构)
ps:要获取javac编译器,可以通过OpenJDK来下载源码,可以自己编译javac的源码,也可以通过调用jdk的com.sun.tools.javac.main.Main类来手动编译指定的类。Javac编译动作的入口是com.sun.tools.javac.main.JavaCompiler类,代码逻辑集中在这个类的compile()和compile2()方法中,整个编译最关键的处理就由图中标注的8个方法来完成,
词法分析器
目的:将源码转换为Token流
流程:
一个字符一个字符的读取源代码,形成规范化的Token流。规范化的Token包含:
- java关键词:package、import、public、class、int等
- 自定义单词:包名、类名、变量名、方法名
- 符号:=、;、+、-、*、/、%、{、}等
源码关键:
词法分析过程是在的JavacParser.parseCompilationUnit()中完成的
com.sun.tools.javac.parser.JavacParser 规定哪些词符合Java语言规范,具体读取和归类不同词法的操作由scanner完成
com.sun.tools.javac.parser.Scanner 负责逐个读取源代码的单个字符,然后解析符合Java语言规范的Token序列,调用一次nextToken()都构造一个Token
com.sun.tools.javac.parser.Tokens$TokenKind 里面包含了所有token的类型,譬如BOOLEAN,BREAK,BYTE,CASE。
com.sun.tools.javac.util.Names 用来存储和表示解析后的词法,每个字符集合都会是一个Name对象,所有的对象都存储在Name.Table这个内部类中。
com.sun.tools.javac.parser.KeyWords 负责将字符集合对应到token集合中,如,package zxy.demo.com; Token.PACKAGE = package, Token.IDENTIFIER = zxy.demo.com,(这部分又分为读取第一个token,为zxy,判断下一个token是否为“.”,是的话接着读取下一个Token.IDENTIFIER类型的token,反复直至下一个token不是”.”,也就是说下一个不是Token.IDENIFIER类型的token,Token.SEMI = ;即这个TIDENTIFIER类型的token的Name读完),KeyWords类负责此任务。
例子:
package compile; public class Cifa { int a; int c = a + 1; }
以上代码转化为的Token流:
语法分析器
目的:将进行词法分析后形成的Token流中的一个个Token组成一句句话,检查这一句句话是不是符合Java语言规范。
流程:
- package
- import
- 类(包含class、interface、enum),一下提到的类泛指这三类,并不单单是指class
源码关键:
com.sun.tools.javac.tree.TreeMaker 所有语法节点都是由它生成的,根据Name对象构建一个语法节点
com.sun.tools.javac.tree.JCTree$JCIf 所有的节点都会继承jctree和实现**tree,譬如 JCIf extends JCTree.JCStatement implements IfTree
com.sun.tools.javac.tree.JCTree的三个属性
- Tree tag:每个语法节点都会以整数的形式表示,下一个节点在上一个节点上加1;
- pos:也是一个整数,它存储的是这个语法节点在源代码中的起始位置,一个文件的位置是0,而-1表示不存在
- type:它代表的是这个节点是什么java类型,如int,float,还是string等
例子:
package compile; public class Yufa { int a; private int c = a + 1; //getter public int getC() { return c; } //setter public void setC(int c) { this.c = c; } }
最终语法树
ps:左边还少一个import的语法节点
说明:
- 每一个包package下的所有类都会放在一个JCCompilationUnit节点下,在该节点下包含:package语法树(作为pid)、各个类的语法树
- 每一个从JCClassDecl发出的分支都是一个完整的代码块,上述是四个分支,对应我们代码中的两行属性操作语句和两个方法块代码块,这样其实就完成了语法分析器的作用:将一个个Token单词组成了一句句话(或者说成一句句代码块)
- 在上述的语法树部分,对于属性操作部分是完整的,但是对于两个方法块,省略了一些语法节点,例如:方法修饰符public、方法返回类型、方法参数。
语义分析器
目的:将语法树转化为注解语法树
流程:
- 添加默认的无参构造器(在没有指定任何有参构造器的情况下),把引用其他类的方法或者变量,抑或是继承实现来的变量和方法等输入到类自身的符号表中
- 处理注解
- 标注:检查语义合法性、进行逻辑判断
- 检查语法树中的变量类型是否匹配(eg.String s = 1 + 2;//这样"="两端的类型就不匹配)
- 检查变量、方法或者类的访问是否合法(eg.一个类无法访问另一个类的private方法)
- 变量在使用前是否已经声明、是否初始化
- 常量折叠(eg.代码中:String s = "hello" + "world",语义分析后String s = "helloworld")
- 推导泛型方法的参数类型
- 数据流分析
- 变量的确定性赋值(eg.有返回值的方法必须确定有返回值)
- final变量只能赋一次值,在编译的时候再赋值的话会报错
- 所有的检查型异常是否抛出或捕获
- 所有的语句都要被执行到(return后边的语句就不会被执行到,除了finally块儿)
- 进一步语义分析
- 去掉永假代码(eg.if(false))
- 变量自动转换(eg.int和Integer)自动装箱拆箱
- 去掉语法糖(eg.foreach转化为for循环,assert转化为if,内部类解析成一个与外部类相关联的外部类)
- 最后,将经过上述处理的语法树转化为最后的注解语法树
源码关键:
com.sun.tools.javac.comp.Enter 将java类中的符号输入到符号表中,主要是两个步骤:
- 将所有类中出现的符号输入到类自身的符号表中,所有类符号、类的参数类型符号(泛型参数类型)、超类符号和继承的接口类型符号等都存储到一个未处理的列表中。
- 将这个未处理的列表中所有的类都解析到各自的类符号列表中,这个操作是在MemberEnter.complete()中完成(默认构造器也是在这里完成的)。
com.sun.tools.javac.processing.JavacProcessingEnvironment 处理注解
com.sun.tools.javac.comp.Attr 检查语义的合理性并进行逻辑判断,类型是否匹配,是否初始化,泛型是否可推导,字符串常量合并
com.sun.tools.javac.comp.Check 协助attr,变量类型是否正确
com.sun.tools.javac.comp.Resolve 协助attr,变量方法类的访问是否合法,是否是静态变量
com.sun.tools.javac.comp.ConstFold 协助attr,常量折叠
com.sun.tools.javac.comp.Infer 协助attr,推导泛型
com.sun.tools.javac.comp.Flow 数据流分析和替换等价源代码的分析(即上面的进一步语义分析)
代码生成器
目的:将注解语法树转化成字节码,并将字节码写入*.class文件。
流程:
- 将java的代码块转化为符合JVM语法的命令形式,这就是字节码
- 按照JVM的文件组织格式将字节码输出到*.class文件中
源码关键:
com.sun.tools.javac.jvm.Gen 遍历语法树生成最终的java字节码
com.sun.tools.javac.jvm.Items 辅助gen,这个类表示任何可寻址的操作项,这些操作项都可以作为一个单位出现在操作栈上
com.sun.tools.javac.jvm.Code 辅助gen,存储生成的字节码,并提供一些能够影射操作码的方法
from: https://www.cnblogs.com/wade-luffy/p/5925728.html
《java虚拟机精讲》读书笔记-第二章字节码的编译原理
jvm同样可以执行其它语言的代码,如ruby,js只要他们编译成符合规范的字节码相关信息可以百度jruby等而java本身编译字节码的工具也就是我们都知道的javac.exe然而我们在eclipse中的编译工作并不是读取jdk执行javac实现的,而是使用... 查看详情
如何用javac和java编译运行整个java工程
如何用javac和java编译运行整个Java工程前言:本文教你怎么用javac和java命令,以及如何利用脚本(shell或bat)方便处理,并用简单的实例展示这些用法。 IDE是把双刃剑,它可以什么都帮你做了,你只要敲几行代码... 查看详情
深入分析java的编译原理
在《Java代码的编译与反编译》中,有过关于Java语言的编译和反编译的介绍。我们可以通过javac命令将Java程序的源代码编译成Java字节码,即我们常说的class文件。这是我们通常意义上理解的编译。但是,字节码并不是机器语言,... 查看详情
java环境变量配置原理解析
...作用是指定命令搜索路径,在命令行下面执行命令如javac编译java程序时,它会到PATH变量所指定的路径中查找看是否能找到相应的命令程序。我们需要把jdk安装目录下的bin目录增加到现有的PATH变量中,bin目录中包含经常要用到的... 查看详情
66.javac编译与jit编译编译过程javac编译词法语法分析填充符号表语义分析字节码生成jit编译
66.javac编译与JIT编译66.1.编译过程66.2.javac编译66.2.1.词法、语法分析66.2.2.填充符号表66.2.3.语义分析66.2.4.字节码生成66.3.JIT编译66.javac编译与JIT编译66.1.编译过程不论是物理机还是虚拟机,大部分的程序代码从开始编译到最终转... 查看详情
Javac:无需注释即可编译的选项
】Javac:无需注释即可编译的选项【英文标题】:Javac:Optiontocompilewithoutnotes【发布时间】:2013-10-0512:19:02【问题描述】:javac是否有编译器选项可以在没有注释的情况下进行编译?我知道:-nowarn:不显示警告-g:none:不生成调试信... 查看详情
jvm原理介绍
...VM ?JVM是执行Java字节码文件(.class)的虚拟机进程。?Java编译器(javac),先将Java源程序(.java)编译成字节码文件(.class),然后由Java虚拟机将字节码文件解释成机器码。最终利用机器码操作硬件和机器系统。?不同的平台有... 查看详情
javac编译过程笔记(代码片段)
Javac前端编译简述这里不讨论JIT编译、AOT编译,本文提到的编译过程仅仅指把.java文件转变为.class文件的过程,这个过程是我们最常见的,通常由Javac编译器来完成。Javac编译器对代码的运行效率几乎没做什么优化,... 查看详情
如何用javac和java编译运行整个java工程
1、将编译好的比如Demo.java文件放到某个盘下(比如D盘)2、进入控制台(window+R键---->输入cmd)3、键入命令D: 回车4、进入D盘之后键入命令:javacDemo.java(要保证你的jdk是安装好的)5、如果没报错,即编译完成,可执行,键入... 查看详情
java编译解释
JDK提供的主要开发工具有:编译程序,解释执行程序、调试程序、Applet执行程序、文档管理程序、包管理程序等。1、编译程序:javac.exe,对应的javac命令将Java源程序转换为字节码。javac[-选项]file.javafile.java:要编译的源文件[-选... 查看详情
Java 注释 - javac 编译器错误?
】Java注释-javac编译器错误?【英文标题】:Javaannotations-javaccompilerbug?【发布时间】:2014-02-2710:20:27【问题描述】:我在嵌套类中的方法参数注释中遇到了一个奇怪的效果。对我来说看起来很像编译器问题。有关重现的详细信息... 查看详情
javac 和 Eclipse 编译器有啥区别?
】javac和Eclipse编译器有啥区别?【英文标题】:WhatisthedifferencebetweenjavacandtheEclipsecompiler?javac和Eclipse编译器有什么区别?【发布时间】:2011-03-0422:47:23【问题描述】:Eclipse的Java编译器只是封装了javac程序所封装的同一个内核,还... 查看详情
jvm系列五(javac编译器)
一、概述我们都知道*.java文件要首先被编译成*.class文件才能被JVM认识,这部分的工作主要由Javac来完成,类似于Javac这样的我们称之为前端编译器;但是*.class文件也不是机器语言,怎么才能让机器识别呢?就需要JVM将*.class文件... 查看详情
jvm系列五(javac编译器).
一、概述我们都知道*.java文件要首先被编译成*.class文件才能被JVM认识,这部分的工作主要由Javac来完成,类似于Javac这样的我们称之为前端编译器;但是*.class文件也不是机器语言,怎么才能让机器识别呢?就需要JVM将*.class文件... 查看详情
javajava即时编译(jit)器原理解析及实践
1.概述转载:Java即时编译(JIT)器原理解析及实践一、导读常见的编译型语言如C++,通常会把代码直接编译成CPU所能理解的机器码来运行。而Java为了实现“一次编译,处处运行”的特性,把编译的过... 查看详情
javac编译过程
Java语言的“编译期”其实是一段“不确定”的操作过程,因为它可能是指一个前端编译器(其实叫“编译器的前端”更准确一些)把*.java文件转变成*.class文件的过程;也可能是指虚拟机的后端运行期编译器(JIT编译器,JustInTimeCompil... 查看详情
java示例代码_使用javac编译Netbeans项目
java示例代码_使用javac编译Netbeans项目 查看详情
为什么javac不在多核上运行?
它看起来很普通,但是javac不能同时编译的技术原因是什么?我读到eclipse编译器是并行的,为什么不用javac呢?答案编译器实际并行工作是非常不寻常的,我找不到任何说明Eclipse的Java编译器是例外的资源。eclipse有能力做的是同... 查看详情