Javac编译原理:基本结构和工作原理

这篇具有很好参考价值的文章主要介绍了Javac编译原理:基本结构和工作原理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Javac编译器

简介

javac是一种编译器,能将一种语言规范转化成另一种语言规范

编译器通常是将便于人理解的语言规范转换成容易理解的语言规范,如C都是将源码直接编译成目标机器码,这个目标机器码是CPU直接执行的指令集合,这些指令集合也就是底层的一种语言规范,机器能够直接识别这种语言规范,虽然这种机器码执行起来高效,但是对人不友好,开发这个代码的成本远远高于省下的机器的执行成本

从某种程度上来说,有了编译器才有了程序语言的繁荣,编译器是人类和机器沟通的一个纽带

javac的任务:将Java源代码语言先转换成JVM能识别的一种语言,再由JVM将JVM语言转化成当前机器能识别的机器语言

Java语言的执行和平台无关,也成就了Java语言的繁荣

Javac编译原理:基本结构和工作原理

从图中可以看出,Javac的任务就是将Java源码编译成Java字节码,也就是JVM能够识别的二进制码,(.java - .class),这些二进制数字是有格式的,只有JVM能正确识别

基本结构

Javac编译器的作用:将符合Java语言规范的源代码转化成符合Java虚拟机规范的Java字节码

如何编译程序

Javac主要四个模块:词法分析器、语法分析器、语义分析器和代码生成器

词法分析:一个字节为一节的读,找出语法关键词,最终从源代码中找出一些规范化的Token流

语法分析:检查关键词组合在一起是否符合Java语言规范,形成一个符合Java语法规范的抽象语法树,抽象语法树是一个结构化的语法表达形式,作用:把语言的主要词法用一个结构化的形式组织在一起,这颗语法树在之后可以按照新的规则重新组织,也是编译器的关键所在

语义分析:把一些难懂的、复杂的语法转换成更加简单的语法,结果就是将复杂语法转换成简单语法,还有注解,形成一个注解过后的抽象语法树,这棵语法树更加接近目标语言的语法规则

代码生成:通过字节码生成器生成字节码,根据经过注解的抽象语法树生成字节码,也就是将一个数据结构转换成另一个数据结构,代码生成器的结果就是生成符合Java虚拟机规范的字节码

Javac编译原理:基本结构和工作原理

工作原理

词法分析器

作用:将Java源文件的字符流转变成对应的Token流

类结构

Javac主要词法分析器的接口类是

package com.sun.tools.javac.parser;
public interface Lexer {

它的默认实现类是

package com.sun.tools.javac.parser;
public class Scanner implements Lexer {

Scanner会逐个读取Java源文件的单个字符,然后解析出符合Java语言规范的Token序列,所涉及的类:

Javac编译原理:基本结构和工作原理

由Factory生成了两个接口的实现类:Scanner和JavacParser,这两个类负责整个词法分析的过程控制;

JavacParser:规定哪些词是符合Java语言规范规定的

Scanner:读取和归类不同词法的操作

Token:规定了所有Java语言的合法关键词

Names:存储和表示解析后的词法

词法分析过程是在JavacParser的该方法中完成的:

    /** CompilationUnit = [ { "@" Annotation } PACKAGE Qualident ";"] {ImportDeclaration} {TypeDeclaration}
     */
    public JCTree.JCCompilationUnit parseCompilationUnit() {
        Token firstToken = token;
        JCModifiers mods = null;
        boolean consumedToplevelDoc = false;
        boolean seenImport = false;
        boolean seenPackage = false;
        ListBuffer<JCTree> defs = new ListBuffer<>();
        //解析修饰符
        if (token.kind == MONKEYS_AT)
            mods = modifiersOpt(); 

        //解析package声明
        if (token.kind == PACKAGE) { 
            int packagePos = token.pos;
            List<JCAnnotation> annotations = List.nil();
            seenPackage = true;
            if (mods != null) {
                checkNoMods(mods.flags & ~Flags.DEPRECATED);
                annotations = mods.annotations;
                mods = null;
            }
            nextToken();
            JCExpression pid = qualident(false);
            accept(SEMI);
            JCPackageDecl pd = toP(F.at(packagePos).PackageDecl(annotations, pid));
            attach(pd, firstToken.comment(CommentStyle.JAVADOC));
            consumedToplevelDoc = true;
            defs.append(pd);
        }

        boolean checkForImports = true;
        boolean firstTypeDecl = true;
        while (token.kind != EOF) {
            if (token.pos <= endPosTable.errorEndPos) {
                // error recovery 跳过错误字符
                skip(checkForImports, false, false, false);
                if (token.kind == EOF)
                    break;
            }
            if (checkForImports && mods == null && token.kind == IMPORT) {
                seenImport = true;
                //解析import声明
                defs.append(importDeclaration());
            } else {
                Comment docComment = token.comment(CommentStyle.JAVADOC);
                if (firstTypeDecl && !seenImport && !seenPackage) {
                    docComment = firstToken.comment(CommentStyle.JAVADOC);
                    consumedToplevelDoc = true;
                }
                //SEMI(";"),
                if (mods != null || token.kind != SEMI)
                    mods = modifiersOpt(mods);
                if (firstTypeDecl && token.kind == IDENTIFIER) {
                    ModuleKind kind = ModuleKind.STRONG;
                    if (token.name() == names.open) {
                        kind = ModuleKind.OPEN;
                        nextToken();
                    }
                    //Token.INDENTIFIER 用于表示用户定义的名称
                    if (token.kind == IDENTIFIER && token.name() == names.module) {
                        if (mods != null) {
                            checkNoMods(mods.flags & ~Flags.DEPRECATED);
                        }
                        defs.append(moduleDecl(mods, kind, docComment));
                        consumedToplevelDoc = true;
                        break;
                    } else if (kind != ModuleKind.STRONG) {
                        reportSyntaxError(token.pos, Errors.ExpectedModule);
                    }
                }
                JCTree def = typeDeclaration(mods, docComment);
                if (def instanceof JCExpressionStatement statement)
                    def = statement.expr;
                defs.append(def);
                if (def instanceof JCClassDecl)
                    checkForImports = false;
                mods = null;
                firstTypeDecl = false;
            }
        }
        JCTree.JCCompilationUnit toplevel = F.at(firstToken.pos).TopLevel(defs.toList());
        if (!consumedToplevelDoc)
            attach(toplevel, firstToken.comment(CommentStyle.JAVADOC));
        if (defs.isEmpty())
            storeEnd(toplevel, S.prevToken().endPos);
        if (keepDocComments)
            toplevel.docComments = docComments;
        if (keepLineMap)
            toplevel.lineMap = S.getLineMap();
        this.endPosTable.setParser(null); // remove reference to parser
        toplevel.endPositions = this.endPosTable;
        return toplevel;
    }

从源文件的一个字符开始,按照Java语法规范依次找出package、import、类定义、属性和方法定义,最后构建一个抽象语法树

Javac是如何分辨一个token的呢?

    /** The factory to be used for abstract syntax tree construction.
     */
    protected TreeMaker F;
	/**
     * Qualident = Ident { DOT [Annotations] Ident }
     */
    public JCExpression qualident(boolean allowAnnos) {
        JCExpression t = toP(F.at(token.pos).Ident(ident()));
        while (token.kind == DOT) { //判断这个token是否token.DOT,如果是则读取整个package定义的类名
            int pos = token.pos;
            nextToken();
            List<JCAnnotation> tyannos = null;
            if (allowAnnos) {
                //注解
                tyannos = typeAnnotationsOpt();
            }
            t = toP(F.at(pos).Select(t, ident()));
            if (tyannos != null && tyannos.nonEmpty()) {
                t = toP(F.at(tyannos.head.pos).AnnotatedType(tyannos, t));
            }
        }
        return t;
    }

先根据Token.INDENTIFIER 的token创建一个JCIdent的语法节点,然后取下一个token,如果为DOT,则进入while循环读取整个路径

如何判断哪些字符组合是一个token则是在Scanner类中定义:

//每调用依次方法就会形成一个token    
public void nextToken() {
        prevToken = token;
        if (!savedTokens.isEmpty()) {
            token = savedTokens.remove(0);
        } else {
            token = tokenizer.readToken();
        }
    }

实际上在读取每个token时都需要一个转换过程,在java源码中的所有字符集合都要找到在com.sun.tools.javac.parser.INDENTIFIER中定义的对应关系,这个任务则是在com.sun.tools.javac.parser.Keywords中完成,Kaywords负责将所有字符集合对应到token集合中

字符集合到token转换相关的类关系:

Javac编译原理:基本结构和工作原理

每个字符集合都是一个Name对象,所有的Name对象都存储在Name.Table这个内部类,这个类也就是对应的这个类的符号表,会将所有的元素按照他们的Token.name转化成name对象,然后建立Name对象和Token的对应关系,保存在Keyworks里的key数组,其他的所有字符集合则会被对应到Token.INDENTIFIER类型

Javac编译原理:基本结构和工作原理

语法分析器

作用:将Token流组建成更加结构化的语法树

语法树的规则

每个语法节点都会实现一个接口xxxTree,这个接口继承自com.sun.source.tree.Tree接口,例如IfTree语法节点表示一个if类型的表达式

每个语法节点都是com.sun.tools.javac.tree.JCTree的子类,并且会实现第一节点中的xxxTree接口类,这个类的名称类似于JCxxx

所有JCxxx 类都作为一个静态内部类定义在JCTree类中

Javac编译原理:基本结构和工作原理

JCTree类有三个重要的属性类

Tree tag :每个语法节点都会用一个整型常数表示,并且每个节点类型的数值都是在前一个的基础上+1,顶层节点TOPLEVEL是1,而IMPORT节点等于TOPLEVEL+1,也就是2

pos :也是一个整数,存储的是这个语法节点在源代码中的起始位置,一个文件的位置是0,-1代表不存在

type :表示这个节点是什么Java类型

例如之前的这个函数

public JCExpression qualident(boolean allowAnnos) {
        JCExpression t = toP(F.at(token.pos).Ident(ident()));
    ...
}

调用了TreeMaker类,根据Name对象构建了一个JCIdent语法节点,如果包名是多级目录,将构建成JCFieldAccess语法节点,此节点也可以是嵌套关系

Package 节点解析完成之后进入while循环,首先解析importDeclaration,解析规则和package类似;解析节点之后构建语法树:

    /** ImportDeclaration = IMPORT [ STATIC ] Ident { "." Ident } [ "." "*" ] ";"
     */
    protected JCTree importDeclaration() {
        int pos = token.pos;
        nextToken();
        boolean importStatic = false;
        //检查是否有static关键字,如果有则设置标识,然后解析第一个类路径,是多级目录则继续读取下一个,并构建JCFiledAccess
        if (token.kind == STATIC) {
            importStatic = true;
            nextToken();
        }
        JCExpression pid = toP(F.at(token.pos).Ident(ident()));
        do {
            int pos1 = token.pos;
            accept(DOT);
            //如果最后一个Token为*,则设置这个JCFieldAccess的Token名称为asterisk
            if (token.kind == STAR) {
                pid = to(F.at(pos1).Select(pid, names.asterisk));
                nextToken();
                break;
            } else {
                pid = toP(F.at(pos1).Select(pid, ident()));
            }
        } while (token.kind == DOT);
        accept(SEMI);
        //最后将这个解析的语法节点作为子结点构建在新创建的JCImport节点中
        return toP(F.at(pos).Import(pid, importStatic));
    }

JCImport语法树如图:

Javac编译原理:基本结构和工作原理

Import节点解析完成之后就是class的解析,类包括interface、class、enum,以class为例:

    /** ClassDeclaration = CLASS Ident TypeParametersOpt [EXTENDS Type]
     *                     [IMPLEMENTS TypeList] ClassBody
     *  @param mods    The modifiers starting the class declaration
     *  @param dc       The documentation comment for the class, or null.
     */
    protected JCClassDecl classDeclaration(JCModifiers mods, Comment dc) {
    	//第一个token是这个类的关键词
        int pos = token.pos;
        accept(CLASS);
        Name name = typeName();
//这个类的类型可选参数,将这个参数解析为JCTypeParameter语法节点
        List<JCTypeParameter> typarams = typeParametersOpt();

        JCExpression extending = null;
        if (token.kind == EXTENDS) {
            nextToken();
            extending = parseType();
        }
        List<JCExpression> implementing = List.nil();
        if (token.kind == IMPLEMENTS) {
            nextToken();
            implementing = typeList();
        }
        //对classBody的解析,也是按照变量定义解析、方法定义解析和内部类定义解析,结果保存在list集合
        List<JCExpression> permitting = permitsClause(mods, "class");
        List<JCTree> defs = classInterfaceOrRecordBody(name, false, false);
//最后将这些子节点添加到JCClassDecl这课class树种
        JCClassDecl result = toP(F.at(pos).ClassDef(
            mods, name, typarams, extending, implementing, permitting, defs));
        attach(result, dc);
        return result;
    }

例如这一段代码:

public class YuFa {
    int a;
    private int c = a + 1;
    
    public int getC() {
        return c;
    }
    
    public void setC(int c) {
        this.c = c;
    }
}

这段代码对应的语法树:

Javac编译原理:基本结构和工作原理

当这个类解析完成之后,会将这个类节点加到这个类对应的包路径的顶层节点也就是JCCompilationUnit,它持有以package 作为pid和JCClassDecl 的集合,这样整个.java文件就被解析完成

所举例代码对应的完整语法树:

Javac编译原理:基本结构和工作原理

注意:所有语法节点的生成都是在TreeMaker类中完成,它实现了在JCTree.Factory 接口中定义的所有节点的构成方法

语义分析器

作用:处理语法树,例如添加默认的构造函数

类:com.sun.tools.javac.comp.Enter

主要完成以下两个步骤

将所有类中出现的符号输入到类自身的符号表中,所有类符号、类的参数类型符号(泛型参数类型)、超类符号和继承的接口类型符号等都存储到一个未处理的列表中

将这个未处理列表中所有的类都解析到各自的类符号列表中,这个操作是在MemberEnter.complete()中完成的

下一个步骤是处理annotation,这个步骤是由com.sun.tools.processing.JavacProssessingEnvironment完成的

再接下来是com.sun.tools.javac.comp.Attr,最重要的是检查语义的合法性并进行逻辑判断,如以下几点:

  • 变量的类型是否匹配
  • 变量在使用前是否已经完成初始化
  • 能够推导出泛型方法的参数类型
  • 字符串常量的合并

在这个步骤中除 Atr 之外还需要另外一些类来协助,如下所述。

  • com.sun.tools.javac.comp.Check:辅助 Attr 类检查语法树中的变量类型是否正确,如二元操作符两边的操作数的类型是否匹配,方法返回的类型是否与接收的引用值类型匹配等。
  • com.sun.tools.javac.comp. Resolve: 主要检查变量、方法或者类的访问是否合法、变量是否是静态变量、变量是否已经初始化等。
  • com.sun.tools.javac.comp.ConstFold:常量折叠,这里主要针对字符串常量,会将一个字符串常量中的多个宇符串合并成一个字符串。
  • com.sun.tools.javac.comp.Infer:帮助推导泛型方法的参数类型等

标注完成后由 com.sun.tools.javac.comp.Flow 类完成数据流分析,数据流分析主要完成如下工作:

  1. 检查变量在使用前是否都己经被正确赋值。除了 Java 中的原始类型,如 int.long、byte、double、char、float,都会有默认的初始化值,其他像String 类型和对象的引用都必须在使用前先赋值。
  2. 保证 final 修饰的变量不会被重复赋值。经过final 修饰的变量只能赋一次值,重复赋值会在这一步编译时报错,如果这个变量是静态变量,则在定义时就必须对其赋值。
  3. 要确定方法的返回值类型。这里需要检查方法的返回值类型是否确定,并检查接受这个方法返回值的引用类型是否匹配,如果没有返回值,则不能有任何引用类型指向方法的这个返回值。
  4. 所有的 Checked Exception 都要捕获或者向上抛出。例如,我们使用 FilelnputStream读取一个文件时,必须捕获可能抛出的FileNotFondException 异常,或者直接向上层方法抛出这个异常。
  5. 所有的语句都要被执行到。这里会检查是否有语句出现在一个return 方法的后面,因为在 return 方法后面的语句永远也不会被执行到。

语法分析的最后一步是执行com.sun.tools.javac.comp.Flow,这是在进一步对语法树进行语义分析,如消除一些无用的代码,总结:

  • 去掉无用的代码
  • 变量的自动转换
  • 去除语法糖

代码生成器

经过语义分析器完成后的语法树已经非常完善了,接下来javac会调用com.sun.tools.javac.jvm.Gen类遍历语法树,生成最终的Java字节码,主要为两个步骤:

将Java方法中的代码块转化成符合JVM语法的命令形式,JVM的操作是基于栈的,所有操作都必须经过出栈和进栈完成

按照JVM的文件组织格式将字节码输出到以class为拓展名的文件

生成字节码除Gen类之外还有两个重要的辅助类:

Items:这个类表示任何可寻址的操作项,包括本地变量、类实例变量或者常量池中用户自定义的常量,这些操作项都可以作为一个单位出现在操作栈上

不同类型的Item对应不同的JVM的操作码:ImmediateItem(常量类型)、LocalItem(本地变量)、StackItem(栈中元素)

Code:存储生成的字节码,并提供一些能够映射操作码的方法

Gen会以后序遍历的顺序解析语法树,将add方法的方法块JCBlock的代码转换成JVM对应的字节码,时序图:

Javac编译原理:基本结构和工作原理

最后使用callMethod方法,返回给方法的调用者文章来源地址https://www.toymoban.com/news/detail-487240.html

到了这里,关于Javac编译原理:基本结构和工作原理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【送书福利-第三十一期】《TVM编译器原理与实践》

    适读人群 :从事AI算法,软件,AI芯片,编译器开发工程技术人员 人工智能(Artificial Intelligence,AI)已经在全世界信息产业中获得广泛应用。深度学习模型推动了AI技术革命,如 TensorFlow、PyTorch、MXNet、Caffe等。大多数现有的系统框架只针对小范围的服务器级 GPU进行过优化,

    2024年01月21日
    浏览(54)
  • Java on VS Code 8月更新|反编译器用户体验优化、新 Maven 项目工作流、代码高亮稳定性提升

    作者:Nick Zhu 排版:Alan Wang 大家好,欢迎来到 Visual Studio Code for Java 的 8 月更新!在这篇博客中,我们将为您提供有关反编译器支持的更多改进。此外,我们将展示如何创建没有原型的 Maven 项目以及一项重要错误修复。让我们开始吧! 上一篇博客中将强大的 Fernflower 反编译

    2024年02月10日
    浏览(47)
  • 计算机体系结构基础知识介绍之缓存性能的十大进阶优化之编译器优化和硬件预取(六)

    处理器和主内存之间不断扩大的性能差距促使编译器编写者仔细检查内存层次结构,看看编译时优化是否可以提高性能。再次,研究分为指令缺失的改进和数据缺失的改进。接下来介绍的优化可以在许多现代编译器中找到。 有些程序具有嵌套循环,以非连续的顺序访问内存中

    2024年02月12日
    浏览(68)
  • 计算机体系结构基础知识介绍之缓存性能的十大进阶优化之编译器控制的预取和利用HBM扩展内存层次(七)

    硬件预取的替代方案是编译器在处理器需要数据之前插入预取指令来请求数据。 预取有两种类型: ■ 寄存器预取将值加载到寄存器中。 ■ 高速缓存预取仅将数据加载到高速缓存。 这两种类型都可以分为有错或无错的,即预取的地址是否会导致虚拟地址错误或保护错误的异

    2024年02月13日
    浏览(53)
  • C++输出编译器名称和版本以及编译器位数、C/C++常见编译器

    常见的C/C++编译器主要包括以下几种: GCC (GNU Compiler Collection):GCC是一个广泛使用的编译器套件,支持多种编程语言,包括C、C++、Objective-C等。它具有强大的优化能力和跨平台支持,并且被广泛应用于各种操作系统和开发环境。 Clang :Clang是基于LLVM的编译器前端,支持C、

    2024年02月13日
    浏览(46)
  • python在线编译器搭建,python在线编译器源码

    本篇文章给大家谈谈python在线编译器搭建,以及python在线编译器源码,希望对各位有所帮助,不要忘了收藏本站喔。 1. PyCharm集成开发环境 2. PyCharm的下载与安装 3. Pycharm的使用 3.1 创建Python项目 3.2 创建子目录 3.3 创建Python文件 3.4 切换解释器 3.5 常用快捷键 4. Pycharm常用配置

    2024年03月25日
    浏览(61)
  • 编译器(Compiler)及C/C++编译器安装(c+安装)

    目录 一、常用编程语言的编译器(compiler) 概述 二、GCC、MinGW、MinGW-w64 、TDM-GCC、Cygwin、MSYS、MSYS2的区别 三、MinGW-w64编译器套件下载及安装 四、MinGW-w64安装后,windows环境变量配置(设置) 五、编译器的运行及其与开发环境的关系、编译器的来源        机器语言是一种计算机指

    2024年02月07日
    浏览(67)
  • 探索Kotlin K2编译器和Java编译器的功能和能力

    文章首发地址 Kotlin K2编译器是Kotlin语言的编译器,负责将Kotlin源代码转换为Java字节码或者其他目标平台的代码。K2编译器是Kotlin语言的核心组件之一,它的主要功能是将Kotlin代码编译为可在JVM上运行的字节码。 编译过程: Kotlin K2编译器将Kotlin源代码作为输入,并经过词法分

    2024年02月11日
    浏览(46)
  • 前端框架编译器之模板编译

    编译原理:是计算机科学的一个分支,研究如何将 高级程序语言 转换为 计算机可执行的目标代码 的技术和理论。 高级程序语言:Python、Java、JavaScript、TypeScript、C、C++、Go 等。 计算机可执行的目标代码:机器码、汇编语言、字节码、目标代码等。 编译器 (Compiler):是一种将

    2024年04月28日
    浏览(48)
  • 【C语言】--编译及编译器

    夫学须静也,才须学也;非学无以广才,非志无以成学 个人主页:【😊个人主页】 系列专栏:【❤️系列专栏】 C语言一直以来都是初入编程的小白们的必修课,作为程序员必学语言之一,C语言自然有属于它的奥秘,接下来就由我来带领大家走进C语言的世界吧🚗🚗🚗 1、

    2024年02月13日
    浏览(56)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包