阅读《深入理解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文件格式版本号
    • 各部分的数量与大小
  • 元数据
    • 类、父类、实现接口的声明信息
    • 属性声明信息
    • 方法声明信息
    • 常量池
  • 方法信息
    • 字节码
    • 异常处理器表
    • 局部变量区的大小
    • 操作数栈的大小
    • 操作数栈的类型记录
    • 调试用符号信息

java代码从出生到执行的过程浅析的更多相关文章

  1. java代码的编译、执行过程

    Java代码编译是由Java源码编译器来完成,流程图如下所示: Java字节码的执行是由JVM执行引擎来完成,流程图如下所示: Java代码编译和执行的整个过程包含了以下三个重要的机制: Java源码 ...

  2. Java代码的编译和执行

    Java代码编译和执行的整个过程包含了以下三个重要的机制: (1)Java源码编译机制 (2)类加载机制 (3)类执行机制 1.Java代码编译是由Java源码编译器来完成,流程图: Java 源码编 ...

  3. 用java代码调用shell脚本执行sqoop将hive表中数据导出到mysql

    1:创建shell脚本 touch sqoop_options.sh chmod 777 sqoop_options.sh 编辑文件  特地将执行map的个数设置为变量  测试 可以java代码传参数 ...

  4. 第3节 sqoop:7、通过java代码远程连接linux执行shell命令

    数据库的数据同步软件sqoop 数据同步 关系型数据库到大数据平台 任务:sqoop 是批量导入数据太慢,如何做到实时的数据同步 实时的数据同步工具: canal 阿里开源的一个数据库数据实时同步的软 ...

  5. java代码从键盘输入执行次数,数,然后排序

    总结:实现从键盘控制执行次数,困惑我很久,直到昨日在提问时,网友说通过循环是肯定可以的所以顿悟了 package com.c2; import java.util.Arrays; import jav ...

  6. 编写高质量java代码151个建议

    http://blog.csdn.net/aishangyutian12/article/details/52699938 第一章  Java开发中通用的方法和准则 建议1:不要在常量和变量中出现易混 ...

  7. Notepad++运行JAVA代码

    第一种方法: 工具栏->运行  点击后选择运行 1.在运行窗口中输入: cmd /k javac "$(FULL_CURRENT_PATH)" & echo 编译成功 ...

  8. Java代码执行过程概述

    Java代码经历三个阶段:源代码阶段(Source) -> 类加载阶段(ClassLoader) -> 运行时阶段(Runtime) 首先我们来理清一下Java代码整个执行过程, 让我们对 ...

  9. java代码的初始化过程研究

        刚刚在ITeye上看到一篇关于java代码初始化的文章,看到代码我试着推理了下结果,虽然是大学时代学的知识了,没想到还能做对.(看来自己大学时掌握的基础还算不错,(*^__^*) 嘻嘻……)但 ...

随机推荐

  1. IntelliJ IDEA竟然出了可以在云端编码的功能?

    前言 自从我用了正版的IntelliJ IDEA后,基本上都是与时俱进,出一个新版本就立马更新,这也能能让我体验到最新最快的功能. 最近在闲逛Jetbrains的官网时,看到了最新的2021.3EAP ...

  2. vue3 学习笔记 (四)——vue3 setup() 高级用法

    本篇文章干货较多,建议收藏! 从 vue2 升级到 vue3,vue3 是可以兼容 vue2 的,所以 vue3 可以采用 vue2 的选项式API.由于选项式API一个变量存在于多处,如果出现问题时 ...

  3. python实现调用摄像头或打开视频文件

    目录: (一)调用摄像头或打开视频文件代码实现 (二)说明和补充 (一)调用摄像头或打开视频文件代码实现 1 # -*- coding=GBK -*- 2 import cv2 as cv 3 4 5 ...

  4. Spring中常用注解

    1.@Component 创建类对象,相当于配置<bean/> 2.@Service @Service与@Component功能相同,写在ServiceImpl类上 3.@Reposito ...

  5. 【备考06组01号】第四届蓝桥杯JAVA组A组国赛题解

    1.填算式 (1)题目描述     请看下面的算式:     (ABCD - EFGH) * XY = 900     每个字母代表一个0~9的数字,不同字母代表不同数字,首位不能为0.     比如 ...

  6. LeeCode刷题笔记

    (本来想在LeeCode题目页面上做注释的,结果没找到位置,只好来这里了) 字符串部分: 14.最长公共前缀:编写一个函数来查找字符串数组中的最长公共前缀. 示例 1: 输入: ["flow ...

  7. MEGAN4,MEGAN5和MEGAN6的Linux安装和使用

    目录 MEGAN 4 MEGAN 5 MEGAN 6 MEGAN(Metagenome Analyzer)是宏基因组学进行物种和功能研究的常用软件,实际上现在的Diamond+MEGAN6已经是一套比 ...

  8. Thinkphp5.1自动加载机制

    Thinkphp5.1自动加载机制 自动加载机制 注册自动加载 引入静态自动加载映射文件,autoload_static.php 根据首字母前缀将不同的加载类归类-$prefixLengthsPsr4 ...

  9. W10: Warning: Changing a readonly file使用vi/vim报错问题解决

    使用vi/vim编辑文件的时候出现W10: Warning: Changing a readonly file报错 解决方法: 一.强制保存退出 :wq! 二.ll 查询文件属主,使用属主赋予权限 c ...

  10. CAS简介

    概念 CAS(Compare And Swap 比较并交换),是 乐观锁 的一种典型实现机制. 乐观锁主要的两个步骤:冲突检测.数据更新. 当多个线程尝试使用CAS同时更新通过一个变量的时候,只有一个 ...