本文涉及的javac编译器来自openjdk.

javac的目录地址为:解压目录/langtools/src/share/classes/com/sun/tools/javac/

javac编译器将Java编译成为一个有效的字节码文件会经历4个步骤:

  • 词法解析:将Java关键字排序,使得程序能有序运行。
  • 语法解析:词法解析后的Token序列整合为一颗抽象的语法树。
  • 语义解析:将抽象语法树扩展地更加完善。
  • 字节码解析:将字节码解析成完整的类。

词法解析


词法解析是编译器执行的字节码编译的第一步。这个步骤中,将Java源码中关键字和标识符等转换成符合规范的Token序列。

词法解析器的接口是com.sun.tools.javac.parser.Lexer ,它直接派生于同包下面的Scanner类,它的主要任务是按照单个字符的方式读取Java源文件中的关键字标识符等,然后将其转换为符合Java规范的Token序列。而负责词法解析工作的是com.sun.tools.javac.parser.JavacParser类,该类的对象实例由ParseFactory负责创建,JavacParser负责词法解析的具体细节

当我们在命令行敲入javac的时候,Java首先会调用com.sun.tools.javac.main.Main类的compile()方法。compile()方法接着就会调用JavaCompiler类的parseFile()方法,parseFile()的主要功能就是调用自己的parse()方法获得JavacParser实例对象,然后调用JavacParser类的parseCompilationUnit()进行词法解析。

这个过程如下图所示:

Token序列

Token其实就是一个枚举类型,其内部定了许多符合Java语法规范并与源码字符集相对应的枚举常量

所有的枚举常量都在 com.sun.tools.javac.parser.Token类中。

编译器在执行词法解析的过程中,只会对Token进行匹配校验。

源码字符集是如何转换成Token的:

Name对象和Token对象建立的是一种一对一的关系。当词法解析器中需要将一个源码字符集合解析成一个Token时,它会通过Names类调用Name类的fromChars()方法获得一个Name对象,然后使用Keyswords类的key(Name name)方法获得传入相对应的Token对象。

词法解析器如何保存源码字符集和Token之间的对应关系:

词法解析器在将源码转字符集合转换为Token之前,会先将每一个字符集合都转换成一个对应的Name对象。接着再由com.sun.tools.javac.parser.Keywords类负责实际的Token转换任务(将Token常量全部转换为Name对象),然后转换好的这些Name对象全部存到Name类的内部类Table中,Keywords类中的数组key用于保存源码字符集合和Token之间的对应关系。

以上两个问题略微有些复杂。画了一个图来表示一下:

调用nextToken()计算Token的获取顺序

Keywords类的key()方法仅仅是根据Name对象获得对应的Token,而词法解析器是通过Scanner类的nextToken()方法保证Token的读取顺序规则。

调用parseCompilationUnit()方法执行词法解析

词法解析的核心是校验Token是否匹配com.sun.tools.javac.parser.JavacParser类在parseCompilationUnit()方法定义的匹配规则。parseCompilationUnit()方法会按照Token的匹配顺序依次解析出package、import等关键字,当这些Token匹配之后,词法解析器会开始解析class主题信息,直到词法解析全部结束,parseCompilationUnit()方法会将Token转换为一棵结构化的抽象语法树。

语法解析


之前提过,语法解析的目的就是将经过词法解析得到的Token整合为一棵结构化的抽象语法树。

词法解析完成的Token序列依旧还不完善,它们还没有被整合起来,语法解析的主要任务是把这些零散的Token按照指定的Java语法规范整合起来形成一个有机的整体。

在语法解析阶段,语法树上每个节点都直接或者间接地继承了JCTree类。

调用qualident()方法解析package语法节点

parseCompilationUnit()这个方法实际上,跨越了词法解析和语法解析两个阶段。当词法解析器成功将package关键字声明转换为Token并完成词法解析之后,会调用qualident()方法根据Token.PACKAGE解析为package语法节点。

语法解析步骤中,com.sun.tools.javac.tree.TreeMaker负责创建JCTree类的所有语法节点对象实例。所以TreeMaker本身就是一个语法解析器,不过具体的细节由parseCompilationUnit()方法来控制。

语法解析器实质上还是使用Token对应的Name对象,来作为转换语法节点的素材。所以在解析语法树之前,首先需要将Token转换成对应的Name对象。语法解析器就可以根据Name对象解析出一个JCIdent语法节点。

当一个package关键字声明中定义了多级目录时,qualident()方法就会循环迭代调用语法解析器将package关键字声明解析为嵌套的JCFieldAccess语法节点。

调用importDeclaration()方法解析import语法树

当成功解析出package语法节点之后,parseCompilationUnit()这个节点会调用importDeclaration()方法来解析得到import语法树。

importDeclaration()首先匹配Token.Static来检测import语句中是否包含了静态导入。接着importDeclaration()就会调用语法解析器的Ident()方法解析出一个JCIdent语法节点,如果import语句中包含多级目录的时候,语法解析器就会调用Select()方法解析为嵌套的JCFieldAccess语法节点。

当语法解析器成功解析出JCIdent和JCFieldAccess节点之后,importDeclaration()方法会调用import()方法,将之前解析的语法节点,整合成为一棵JCImport节点。

实际开发中,通常会有多个import关键字声明,那么importDeclaration()方法内部会通过迭代循环方法解析出多个JCImport语法树,然后将其存储在一个集合中。

调用classDeclaration()方法解析class语法树

语法解析的最后一步就是解析class的主体信息,当语法解析器成功将import关键字解析声明为JCIdent和JCFieldAccess语法节点并整合为一颗JCImport语法树之后,parseCompilationUnit()方法内部通过typeDeclaration()方法调用classOrInterfaceOrEnumDeclaration方法将class主体信息解析为一棵JCClassDecl语法树。

当以上这个过程结束之后,parseCompilationUnit()方法会调用TopLevel()方法将之前解析好过的package语法节点,import语法节点和class语法树等内容内容信息全部整合成一棵JCCompilationUnit语法节点树。

JCCompilationUnit类会成为整个语法树的Root,持有整个语法树的所有节点。这个时候,语法树的雏形已经建成。

语义解析


经过了以上两个步骤,解析完成的语法树依旧不能进入字节码的编译,它还不够完善。语义解析的任务就是将这个这颗不够完善的语法树扩充地更加完善。

语义解析步骤中经历的操作:

  • 为没有构造方法的类型添加默认的无参构造函数
  • 检查任何变量是否在使用之前都被初始化
  • 检查变量类型和值是不是匹配
  • 将String类型和常量进行合并处理
  • 检查代码中的操作是不是都可达
  • 异常检查
  • 将语法糖的内容正常化

常量折叠操作

如果一个String类型的数据是由多个常量通过『+』组成的,它其实只会创建一个String对象,编译器在语义解析的时候,会将多个常量信息合并为一个对象。

//源代码中的写法
String str="Hello"+" "+"World!"
//经过编译器编译之后
String str="Hello World!"

生成字节码


在经历了一系列的语义解析之后,所解析出来的语法树就足够完善了。这个时候编译器最后的任务就是调用com.sun.tools.javac.jvm.Gen类,将这棵语法树编译为Java字节码文件。

这个时候,符合Java规范的Java代码就转换成符合Java规范的字节码文件了。

从Java文件到字节码文件的更多相关文章

  1. Java代码到字节码——第一部分

    Java代码到字节码——第一部分 原文地址 作者:James Bloom 译者:张坤 理解在Java虚拟机中Java代码如何别被编译成字节码并执行是非常重要的,因为这可以帮助你理解你的程序在运行时发生 ...

  2. 从Java代码到字节码(1)

    理解Java代码是如何被编译为字节码并在Java虚拟机(JVM)上执行是非常重要的,这将帮助理解你的程序是如何执行的.这样的理解不仅仅能够让你在逻辑上更好的掌握语言特性,而且能够有机会理解在做出重要决 ...

  3. 从Java代码到字节码

    http://www.importnew.com/13107.html http://blog.csdn.net/dc_726/article/details/7944154/ http://www. ...

  4. 浅谈Java反射机制 之 获取类的字节码文件 Class.forName("全路径名") 、getClass()、class

    另一个篇:获取 类 的 方法 和 属性(包括构造函数) 先贴上Java反射机制的概念: AVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法: 对于任意一个对象,都能够调用它 ...

  5. OpenJDK源码研究笔记(八)-详细解析如何读取Java字节码文件(.class)

    在上一篇OpenJDK源码研究笔记(七)–Java字节码文件(.class)的结构中,我们大致了解了Java字节码文件的结构. 本篇详细地介绍了如何读取.class文件的大部分细节. 1.构造文件  ...

  6. 玩命学JVM(一)—认识JVM和字节码文件

    本篇文章的思维导图 一.JVM的简单介绍 1.1 JVM是什么? JVM (java virtual machine),java虚拟机,是一个虚构出来的计算机,但是有自己完善的硬件结构:处理器.堆栈. ...

  7. Matlab匿名函数,向量化和预分配,函数的函数,P码文件

    匿名函数: 匿名函数是不存储在程序文件中.但与数据类型是 function_handle 的变量相关的函数.匿名函数可以接受输入并返回输出,就像标准函数一样.但是,它们可能只包含一个可执行语句. 例如 ...

  8. Java Eclipse编译后产生的字节码文件,用DOS命令符怎么打开

    在很多初学者刚刚接触eclipse的时候,写完一个代码文件.例如 Demo.java 通过run as a java application生成之后,会产生一个Demo.class. Demo.cla ...

  9. java字节码文件 helloworld

    Java代码 \\A.java public class A{} 1 2 1 2 javac A.java \\得到 A.class javap -v A.class 下面是javap工具帮我们生成的 ...

随机推荐

  1. ASP.NET Aries 入门开发教程9:业务表单的开发

    前言: 经过前面那么多篇的列表的介绍,终于到了大伙期待的表单开发了. 也是本系列的最后一篇文章了! 1:表单页面的权限设置与继承 对于表单页面,权限的设置有两种: 1:你可以选择添加菜单(设置为不显示 ...

  2. ASP.NET Aries 入门开发教程2:配置出一个简单的列表页面

    前言: 朋友们都期待我稳定地工作,但创业公司若要躺下,也非意念可控. 若人生注定了风雨飘摇,那就雨中前行了. 最机开始看聊新的工作机会,欢迎推荐,创业公司也可! 同时,趁着自由时间,抓紧把这系列教程给 ...

  3. Chrome出了个小bug:论如何在Chrome下劫持原生只读对象

    Chrome出了个小bug:论如何在Chrome下劫持原生只读对象 概述 众所周知,虽然JavaScript是个很灵活的语言,浏览器里很多原生的方法都可以随意覆盖或者重写,比如alert.但是为了保证 ...

  4. Android数据加密之SHA安全散列算法

    前言: 对于SHA安全散列算法,以前没怎么使用过,仅仅是停留在听说过的阶段,今天在看图片缓存框架Glide源码时发现其缓存的Key采用的不是MD5加密算法,而是SHA-256加密算法,这才勾起了我的好 ...

  5. WebGIS中等值面展示的相关方案简析

    文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/ 1.背景 等值面是气象.环保等相关项目上常用到的效果展示.在传统的CS项 ...

  6. C++随笔:.NET CoreCLR之corleCLR核心探索之coreconsole(2)

    这篇文章是上篇的续集,本文将会继续介绍coreconsole.cpp里面的逻辑.也许大家会看一些CLR的书,我承认我没有看过,因为我觉得一个人,他再NB,那也是他自己的眼光,而且说句难听的,CLR也不 ...

  7. [原] KVM 虚拟化原理探究(3)— CPU 虚拟化

    KVM 虚拟化原理探究(3)- CPU 虚拟化 标签(空格分隔): KVM [TOC] CPU 虚拟化简介 上一篇文章笼统的介绍了一个虚拟机的诞生过程,从demo中也可以看到,运行一个虚拟机再也不需要 ...

  8. 初识的Spring Mvc-----原理

    一.Spring Mvc简介 Spring Mvc(Spring Web Mvc) 属于表现层的框架. 二.Spring结构图 Spring Mvc是Spring框架里面web模块的一部分,是在Spr ...

  9. arcpy+PyQt+py2exe快速开发桌面端ArcGIS应用程序

    前段时间有一个项目,大体是要做一个GIS数据处理工具. 一般的方法是基于ArcObjects来进行开发,因为我对ArcObjects不太熟悉,所以就思考有没有其他简单快速的方法来做. 在查看ArcGI ...

  10. django 第三天 有关库使用

    项目中经常会用到第三方的lib和app,有些lib和app会进行不断更新,更新后可能会存在冲突,因此可以创建externals目录,下面欧app和libs.app存放django-cms,haysta ...