java代码从出生到执行的过程浅析
阅读《深入理解java虚拟机 第二版 JVM高级特性与最佳实践》 - jdk版本为1.6
1.什么是编译型语言、解释型语言
解释型语言:源代码不是直接翻译成机器语言,而是先翻译成中间代码,再由解释器对中间代码进行解释运行。比如Python/JavaScript / Perl /Shell等都是解释型语言。
编译型语言:程序不需要编译,程序在运行时才翻译成机器语言,每执行一次都要翻译一次。像C/C++等都是编译型语言。
java是比较特殊的存在,java从源程序到真正执行需要经历从.java文件到.class文件的编译(前端编译,对应的编译器有javac,eclipse的ECJ),.class文件到二进制机器码的解释执行(后端编译,对应的编译器有JIT);还有一种能够直接把java文件编译成本地机器代码的静态提前编译器(AOT编译器)。
大致的执行路线如下图:
java语言的"编译期"分为前端编译和后端编译两个阶段。前端编译是指把*.java文件转变成*.class文件的过程; 后端编译(JIT, Just In Time Compiler)是指把字节码转变成机器码的过程。
2.Java中的前端编译
2.1词法分析
先弄懂一个概念:token:计算机科学中将字符序列转换为单词(Token)序列的过程
我们把执行词法分析的函数称为词法分析器。它的主要功能是将源代码的字符流转变为Token集合, Token则是编译过程的最小单位。
规范化的Token包含:
- java关键词:package、import、public、class、int等
- 自定义单词(标识符):包名、类名、变量名、方法名
- 符号:=、;、+、-、*、/、%、{、}等
有一个例子,我们直接拿过来:
package compile;public class Cifa {
int a;
int c = a + 1;
}
以上代码转化为的Token流:
我们可以看到每个token中的数据结构:token的类型及token的具体的值
- Javac是如何分辨出一个个的Token
Javac进行词法分析时会根据java语言规范来控制什么顺序,在什么地方应该出现什么Token(例如对package的读取:在创建javacParsepackage对象的构造函数时,Scanner会读取第一个Token(Token.PACKAGE),而词法分析器的整个过程是在javacParser的parsrCompilationUnit方法中完成的,先判断当前的Token是不是Token.PACKAGE(是的话读取package的定义),接着读取下一个Token(IDENTIFIER),再读取类名时如果遇到Token.Dot也就是‘.’将继续往下读,直到读得完成类名即遇到Token.SEMI(“;”)为止)。由此可以看出,读取哪个Token是由javacParser规定的而Token流的顺序要符合java语言规范。
- Javac如何确认一个Token的
如何确定字符组合是一个Token的规则是在Scanner的nextToken方法中定义的,每调用该方法一次就会构造一个Token,而这些Token必然是com.sun.tools.javac.parser.Token中的任何元素之一。
在读取每个Token时都需要一个转换过程(如在package中的"compile"包名要转化成Token.IDENTIFIER类型),在Java源码中的所有字符集合都要找到在com.sun.tools.javac.parser.Token中定义的对应关系,这个任务是在com.sun.tools.javac.parser.Keywords类中完成的,Keywords负责将所有字符集合对应到Token流中
2.2语法分析
我们得到了token的集合之后,在语法分析器中,将进行词法分析后形成的Token流中的一个个Token组成一句句话,检查这一句句话是不是符合Java语言规范。
每个语法树上的语法节点都是com.sun.tools.javac.tree.JCTree的实例,语法树的一些规则如下:
1.每个语法节点都会实现一个xxxTree接口,该接口继承自com.sun.source.tree.Tree接口。如IfTree语法节点表示一个if类型表达式
2.每个语法节点都是com.sun.tools.javac.tree.JCTree的子类并且会实现1中提及的接口类,这个类的类名类似于JCxxx类,如实现IfTree接口的实现类为JCIf
3.所有的JCxxx类都作为一个静态内部类定义在JCTree类中
JCTree类中有如下三个重要的属性
1.Tree tag:每个节点都会用一个整形常数表示,并且每个节点的类型的数值是前一个节点的类型数值加1
2.pos:也是一个整数,表示语法节点在源文件中的起始位置,文件的起始位置为0,-1的话表示不存在
3.type:表示这个语法节点是什么类型,如int、float还是String
2.3语法树解析
package解析:JCIdent=>JCFieldAccess
根据Name对象构建了一个JCIdent语法节点,如果是多级目录,将构建JCFieldAccess语法节点,JCFieldAccess语法节点可以是嵌套关系
import解析:JCIdent=>JCFieldAccess=>JCFieldAccess=>JCIdent
当成功解析出package语法节点之后,parseCompilationUnit()这个节点会调用importDeclaration()方法来解析得到import语法树。
importDeclaration()首先检查Token是不是Token.IMPORT,如果是则构造一个语法树,再匹配是不是有static关键字看看是不是静态引入。接着importDeclaration()就会调用语法解析器的Ident()方法解析出一个JCIdent语法节点,如果import语句中包含多级目录的时候,语法解析器就会调用Select()方法解析为嵌套的JCFieldAccess语法节点。
当语法解析器成功解析出JCIdent和JCFieldAccess节点之后,importDeclaration()方法会调用Import()方法,将之前解析的语法节点,整合成为一棵JCImport节点。
实际开发中,通常会有多个import关键字声明,那么importDeclaration()方法内部会通过迭代循环方法解析出多个JCImport语法树,然后将其存储在一个集合中。
class解析:
Import节点解析完之后就是累的解析(interface、class、enum),以class为例
package compile;
public class Yufa {
int a;
private int c=a+1;
public int getC(){
return c;
}
public void setC(int c){
this.c=c;
}
}
第一个Token是Token.CLASS这个类的关键词,接下来是用户定义Token.IDENTIFIER,也是类名。然后是参数,下一个是Token.EXTENDS或者Token.IMPLEMENTS,接着是对classbody的解析,这个classbody解析的结果保存在list集合中,最后将这些子节点添加到JCClassDecl这颗class树中。
这个类解析完成之后,会把这些子树加到顶层语法节点JCCompilationUnit(以package作为pid并且持有JCClassDecl语法节点的集合)之下,完整的语法树如下:
2.3语义分析
作用:将语法树转化为注解语法树
- 添加默认的无参构造器(在没有指定任何有参构造器的情况下)
- 处理注解
- 标注:检查语义合法性、进行逻辑判断
- 检查语法树中的变量类型是否匹配(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,内部类解析成一个与外部类相关联的外部类)
- 最后,将经过上述处理的语法树转化为最后的注解语法树
2.4代码生成
作用:将注解语法树转化成字节码,并将字节码写入*.class文件。
- 将java的代码块转化为符合JVM语法的命令形式,这就是字节码
- 按照JVM的文件组织格式将字节码输出到*.class文件中
在生成的*.class文件中不只包含字节码信息,具体包含:
- 结构信息
- class文件格式版本号
- 各部分的数量与大小
- 元数据
- 类、父类、实现接口的声明信息
- 属性声明信息
- 方法声明信息
- 常量池
- 方法信息
- 字节码
- 异常处理器表
- 局部变量区的大小
- 操作数栈的大小
- 操作数栈的类型记录
- 调试用符号信息
引用:
https://www.cnblogs.com/java-zhao/p/5194064.html
https://www.cnblogs.com/tianming1223/p/10206903.html
https://www.jianshu.com/p/04d9f9d519a6
http://nathanchen.github.io/14599206151015.html
以上是 java代码从出生到执行的过程浅析 的全部内容, 来源链接: utcz.com/z/392849.html