国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Javac編譯原理:基本結(jié)構(gòu)和工作原理

這篇具有很好參考價(jià)值的文章主要介紹了Javac編譯原理:基本結(jié)構(gòu)和工作原理。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

Javac編譯器

簡介

javac是一種編譯器,能將一種語言規(guī)范轉(zhuǎn)化成另一種語言規(guī)范

編譯器通常是將便于人理解的語言規(guī)范轉(zhuǎn)換成容易理解的語言規(guī)范,如C都是將源碼直接編譯成目標(biāo)機(jī)器碼,這個(gè)目標(biāo)機(jī)器碼是CPU直接執(zhí)行的指令集合,這些指令集合也就是底層的一種語言規(guī)范,機(jī)器能夠直接識別這種語言規(guī)范,雖然這種機(jī)器碼執(zhí)行起來高效,但是對人不友好,開發(fā)這個(gè)代碼的成本遠(yuǎn)遠(yuǎn)高于省下的機(jī)器的執(zhí)行成本

從某種程度上來說,有了編譯器才有了程序語言的繁榮,編譯器是人類和機(jī)器溝通的一個(gè)紐帶

javac的任務(wù):將Java源代碼語言先轉(zhuǎn)換成JVM能識別的一種語言,再由JVM將JVM語言轉(zhuǎn)化成當(dāng)前機(jī)器能識別的機(jī)器語言

Java語言的執(zhí)行和平臺無關(guān),也成就了Java語言的繁榮

Javac編譯原理:基本結(jié)構(gòu)和工作原理

從圖中可以看出,Javac的任務(wù)就是將Java源碼編譯成Java字節(jié)碼,也就是JVM能夠識別的二進(jìn)制碼,(.java - .class),這些二進(jìn)制數(shù)字是有格式的,只有JVM能正確識別

基本結(jié)構(gòu)

Javac編譯器的作用:將符合Java語言規(guī)范的源代碼轉(zhuǎn)化成符合Java虛擬機(jī)規(guī)范的Java字節(jié)碼

如何編譯程序

Javac主要四個(gè)模塊:詞法分析器、語法分析器、語義分析器和代碼生成器

詞法分析:一個(gè)字節(jié)為一節(jié)的讀,找出語法關(guān)鍵詞,最終從源代碼中找出一些規(guī)范化的Token流

語法分析:檢查關(guān)鍵詞組合在一起是否符合Java語言規(guī)范,形成一個(gè)符合Java語法規(guī)范的抽象語法樹,抽象語法樹是一個(gè)結(jié)構(gòu)化的語法表達(dá)形式,作用:把語言的主要詞法用一個(gè)結(jié)構(gòu)化的形式組織在一起,這顆語法樹在之后可以按照新的規(guī)則重新組織,也是編譯器的關(guān)鍵所在

語義分析:把一些難懂的、復(fù)雜的語法轉(zhuǎn)換成更加簡單的語法,結(jié)果就是將復(fù)雜語法轉(zhuǎn)換成簡單語法,還有注解,形成一個(gè)注解過后的抽象語法樹,這棵語法樹更加接近目標(biāo)語言的語法規(guī)則

代碼生成:通過字節(jié)碼生成器生成字節(jié)碼,根據(jù)經(jīng)過注解的抽象語法樹生成字節(jié)碼,也就是將一個(gè)數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)換成另一個(gè)數(shù)據(jù)結(jié)構(gòu),代碼生成器的結(jié)果就是生成符合Java虛擬機(jī)規(guī)范的字節(jié)碼

Javac編譯原理:基本結(jié)構(gòu)和工作原理

工作原理

詞法分析器

作用:將Java源文件的字符流轉(zhuǎn)變成對應(yīng)的Token流

類結(jié)構(gòu)

Javac主要詞法分析器的接口類是

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

它的默認(rèn)實(shí)現(xiàn)類是

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

Scanner會逐個(gè)讀取Java源文件的單個(gè)字符,然后解析出符合Java語言規(guī)范的Token序列,所涉及的類:

Javac編譯原理:基本結(jié)構(gòu)和工作原理

由Factory生成了兩個(gè)接口的實(shí)現(xiàn)類:Scanner和JavacParser,這兩個(gè)類負(fù)責(zé)整個(gè)詞法分析的過程控制;

JavacParser:規(guī)定哪些詞是符合Java語言規(guī)范規(guī)定的

Scanner:讀取和歸類不同詞法的操作

Token:規(guī)定了所有Java語言的合法關(guān)鍵詞

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;
    }

從源文件的一個(gè)字符開始,按照J(rèn)ava語法規(guī)范依次找出package、import、類定義、屬性和方法定義,最后構(gòu)建一個(gè)抽象語法樹

Javac是如何分辨一個(gè)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) { //判斷這個(gè)token是否token.DOT,如果是則讀取整個(gè)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;
    }

先根據(jù)Token.INDENTIFIER 的token創(chuàng)建一個(gè)JCIdent的語法節(jié)點(diǎn),然后取下一個(gè)token,如果為DOT,則進(jìn)入while循環(huán)讀取整個(gè)路徑

如何判斷哪些字符組合是一個(gè)token則是在Scanner類中定義:

//每調(diào)用依次方法就會形成一個(gè)token    
public void nextToken() {
        prevToken = token;
        if (!savedTokens.isEmpty()) {
            token = savedTokens.remove(0);
        } else {
            token = tokenizer.readToken();
        }
    }

實(shí)際上在讀取每個(gè)token時(shí)都需要一個(gè)轉(zhuǎn)換過程,在java源碼中的所有字符集合都要找到在com.sun.tools.javac.parser.INDENTIFIER中定義的對應(yīng)關(guān)系,這個(gè)任務(wù)則是在com.sun.tools.javac.parser.Keywords中完成,Kaywords負(fù)責(zé)將所有字符集合對應(yīng)到token集合中

字符集合到token轉(zhuǎn)換相關(guān)的類關(guān)系:

Javac編譯原理:基本結(jié)構(gòu)和工作原理

每個(gè)字符集合都是一個(gè)Name對象,所有的Name對象都存儲在Name.Table這個(gè)內(nèi)部類,這個(gè)類也就是對應(yīng)的這個(gè)類的符號表,會將所有的元素按照他們的Token.name轉(zhuǎn)化成name對象,然后建立Name對象和Token的對應(yīng)關(guān)系,保存在Keyworks里的key數(shù)組,其他的所有字符集合則會被對應(yīng)到Token.INDENTIFIER類型

Javac編譯原理:基本結(jié)構(gòu)和工作原理

語法分析器

作用:將Token流組建成更加結(jié)構(gòu)化的語法樹

語法樹的規(guī)則

每個(gè)語法節(jié)點(diǎn)都會實(shí)現(xiàn)一個(gè)接口xxxTree,這個(gè)接口繼承自com.sun.source.tree.Tree接口,例如IfTree語法節(jié)點(diǎn)表示一個(gè)if類型的表達(dá)式

每個(gè)語法節(jié)點(diǎn)都是com.sun.tools.javac.tree.JCTree的子類,并且會實(shí)現(xiàn)第一節(jié)點(diǎn)中的xxxTree接口類,這個(gè)類的名稱類似于JCxxx

所有JCxxx 類都作為一個(gè)靜態(tài)內(nèi)部類定義在JCTree類中

Javac編譯原理:基本結(jié)構(gòu)和工作原理

JCTree類有三個(gè)重要的屬性類

Tree tag :每個(gè)語法節(jié)點(diǎn)都會用一個(gè)整型常數(shù)表示,并且每個(gè)節(jié)點(diǎn)類型的數(shù)值都是在前一個(gè)的基礎(chǔ)上+1,頂層節(jié)點(diǎn)TOPLEVEL是1,而IMPORT節(jié)點(diǎn)等于TOPLEVEL+1,也就是2

pos :也是一個(gè)整數(shù),存儲的是這個(gè)語法節(jié)點(diǎn)在源代碼中的起始位置,一個(gè)文件的位置是0,-1代表不存在

type :表示這個(gè)節(jié)點(diǎn)是什么Java類型

例如之前的這個(gè)函數(shù)

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

調(diào)用了TreeMaker類,根據(jù)Name對象構(gòu)建了一個(gè)JCIdent語法節(jié)點(diǎn),如果包名是多級目錄,將構(gòu)建成JCFieldAccess語法節(jié)點(diǎn),此節(jié)點(diǎn)也可以是嵌套關(guān)系

Package 節(jié)點(diǎn)解析完成之后進(jìn)入while循環(huán),首先解析importDeclaration,解析規(guī)則和package類似;解析節(jié)點(diǎn)之后構(gòu)建語法樹:

    /** ImportDeclaration = IMPORT [ STATIC ] Ident { "." Ident } [ "." "*" ] ";"
     */
    protected JCTree importDeclaration() {
        int pos = token.pos;
        nextToken();
        boolean importStatic = false;
        //檢查是否有static關(guān)鍵字,如果有則設(shè)置標(biāo)識,然后解析第一個(gè)類路徑,是多級目錄則繼續(xù)讀取下一個(gè),并構(gòu)建JCFiledAccess
        if (token.kind == STATIC) {
            importStatic = true;
            nextToken();
        }
        JCExpression pid = toP(F.at(token.pos).Ident(ident()));
        do {
            int pos1 = token.pos;
            accept(DOT);
            //如果最后一個(gè)Token為*,則設(shè)置這個(gè)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);
        //最后將這個(gè)解析的語法節(jié)點(diǎn)作為子結(jié)點(diǎn)構(gòu)建在新創(chuàng)建的JCImport節(jié)點(diǎn)中
        return toP(F.at(pos).Import(pid, importStatic));
    }

JCImport語法樹如圖:

Javac編譯原理:基本結(jié)構(gòu)和工作原理

Import節(jié)點(diǎn)解析完成之后就是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) {
    	//第一個(gè)token是這個(gè)類的關(guān)鍵詞
        int pos = token.pos;
        accept(CLASS);
        Name name = typeName();
//這個(gè)類的類型可選參數(shù),將這個(gè)參數(shù)解析為JCTypeParameter語法節(jié)點(diǎn)
        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的解析,也是按照變量定義解析、方法定義解析和內(nèi)部類定義解析,結(jié)果保存在list集合
        List<JCExpression> permitting = permitsClause(mods, "class");
        List<JCTree> defs = classInterfaceOrRecordBody(name, false, false);
//最后將這些子節(jié)點(diǎn)添加到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;
    }
}

這段代碼對應(yīng)的語法樹:

Javac編譯原理:基本結(jié)構(gòu)和工作原理

當(dāng)這個(gè)類解析完成之后,會將這個(gè)類節(jié)點(diǎn)加到這個(gè)類對應(yīng)的包路徑的頂層節(jié)點(diǎn)也就是JCCompilationUnit,它持有以package 作為pid和JCClassDecl 的集合,這樣整個(gè).java文件就被解析完成

所舉例代碼對應(yīng)的完整語法樹:

Javac編譯原理:基本結(jié)構(gòu)和工作原理

注意:所有語法節(jié)點(diǎn)的生成都是在TreeMaker類中完成,它實(shí)現(xiàn)了在JCTree.Factory 接口中定義的所有節(jié)點(diǎn)的構(gòu)成方法

語義分析器

作用:處理語法樹,例如添加默認(rèn)的構(gòu)造函數(shù)

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

主要完成以下兩個(gè)步驟

將所有類中出現(xiàn)的符號輸入到類自身的符號表中,所有類符號、類的參數(shù)類型符號(泛型參數(shù)類型)、超類符號和繼承的接口類型符號等都存儲到一個(gè)未處理的列表中

將這個(gè)未處理列表中所有的類都解析到各自的類符號列表中,這個(gè)操作是在MemberEnter.complete()中完成的

下一個(gè)步驟是處理annotation,這個(gè)步驟是由com.sun.tools.processing.JavacProssessingEnvironment完成的

再接下來是com.sun.tools.javac.comp.Attr,最重要的是檢查語義的合法性并進(jìn)行邏輯判斷,如以下幾點(diǎn):

  • 變量的類型是否匹配
  • 變量在使用前是否已經(jīng)完成初始化
  • 能夠推導(dǎo)出泛型方法的參數(shù)類型
  • 字符串常量的合并

在這個(gè)步驟中除 Atr 之外還需要另外一些類來協(xié)助,如下所述。

  • com.sun.tools.javac.comp.Check:輔助 Attr 類檢查語法樹中的變量類型是否正確,如二元操作符兩邊的操作數(shù)的類型是否匹配,方法返回的類型是否與接收的引用值類型匹配等。
  • com.sun.tools.javac.comp. Resolve: 主要檢查變量、方法或者類的訪問是否合法、變量是否是靜態(tài)變量、變量是否已經(jīng)初始化等。
  • com.sun.tools.javac.comp.ConstFold:常量折疊,這里主要針對字符串常量,會將一個(gè)字符串常量中的多個(gè)宇符串合并成一個(gè)字符串。
  • com.sun.tools.javac.comp.Infer:幫助推導(dǎo)泛型方法的參數(shù)類型等

標(biāo)注完成后由 com.sun.tools.javac.comp.Flow 類完成數(shù)據(jù)流分析,數(shù)據(jù)流分析主要完成如下工作:

  1. 檢查變量在使用前是否都己經(jīng)被正確賦值。除了 Java 中的原始類型,如 int.long、byte、double、char、float,都會有默認(rèn)的初始化值,其他像String 類型和對象的引用都必須在使用前先賦值。
  2. 保證 final 修飾的變量不會被重復(fù)賦值。經(jīng)過final 修飾的變量只能賦一次值,重復(fù)賦值會在這一步編譯時(shí)報(bào)錯,如果這個(gè)變量是靜態(tài)變量,則在定義時(shí)就必須對其賦值。
  3. 要確定方法的返回值類型。這里需要檢查方法的返回值類型是否確定,并檢查接受這個(gè)方法返回值的引用類型是否匹配,如果沒有返回值,則不能有任何引用類型指向方法的這個(gè)返回值。
  4. 所有的 Checked Exception 都要捕獲或者向上拋出。例如,我們使用 FilelnputStream讀取一個(gè)文件時(shí),必須捕獲可能拋出的FileNotFondException 異常,或者直接向上層方法拋出這個(gè)異常。
  5. 所有的語句都要被執(zhí)行到。這里會檢查是否有語句出現(xiàn)在一個(gè)return 方法的后面,因?yàn)樵?return 方法后面的語句永遠(yuǎn)也不會被執(zhí)行到。

語法分析的最后一步是執(zhí)行com.sun.tools.javac.comp.Flow,這是在進(jìn)一步對語法樹進(jìn)行語義分析,如消除一些無用的代碼,總結(jié):

  • 去掉無用的代碼
  • 變量的自動轉(zhuǎn)換
  • 去除語法糖

代碼生成器

經(jīng)過語義分析器完成后的語法樹已經(jīng)非常完善了,接下來javac會調(diào)用com.sun.tools.javac.jvm.Gen類遍歷語法樹,生成最終的Java字節(jié)碼,主要為兩個(gè)步驟:

將Java方法中的代碼塊轉(zhuǎn)化成符合JVM語法的命令形式,JVM的操作是基于棧的,所有操作都必須經(jīng)過出棧和進(jìn)棧完成

按照J(rèn)VM的文件組織格式將字節(jié)碼輸出到以class為拓展名的文件

生成字節(jié)碼除Gen類之外還有兩個(gè)重要的輔助類:

Items:這個(gè)類表示任何可尋址的操作項(xiàng),包括本地變量、類實(shí)例變量或者常量池中用戶自定義的常量,這些操作項(xiàng)都可以作為一個(gè)單位出現(xiàn)在操作棧上

不同類型的Item對應(yīng)不同的JVM的操作碼:ImmediateItem(常量類型)、LocalItem(本地變量)、StackItem(棧中元素)

Code:存儲生成的字節(jié)碼,并提供一些能夠映射操作碼的方法

Gen會以后序遍歷的順序解析語法樹,將add方法的方法塊JCBlock的代碼轉(zhuǎn)換成JVM對應(yīng)的字節(jié)碼,時(shí)序圖:

Javac編譯原理:基本結(jié)構(gòu)和工作原理

最后使用callMethod方法,返回給方法的調(diào)用者文章來源地址http://www.zghlxwxcb.cn/news/detail-487240.html

到了這里,關(guān)于Javac編譯原理:基本結(jié)構(gòu)和工作原理的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包