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的任務(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é)碼
工作原理
詞法分析器
作用:將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序列,所涉及的類:
由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)系:
每個(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類型
語法分析器
作用:將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類中
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語法樹如圖:
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)的語法樹:
當(dāng)這個(gè)類解析完成之后,會將這個(gè)類節(jié)點(diǎn)加到這個(gè)類對應(yīng)的包路徑的頂層節(jié)點(diǎn)也就是JCCompilationUnit
,它持有以package 作為pid和JCClassDecl 的集合,這樣整個(gè).java
文件就被解析完成
所舉例代碼對應(yīng)的完整語法樹:
注意:所有語法節(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ù)流分析主要完成如下工作:
- 檢查變量在使用前是否都己經(jīng)被正確賦值。除了 Java 中的原始類型,如 int.long、byte、double、char、float,都會有默認(rèn)的初始化值,其他像String 類型和對象的引用都必須在使用前先賦值。
- 保證 final 修飾的變量不會被重復(fù)賦值。經(jīng)過final 修飾的變量只能賦一次值,重復(fù)賦值會在這一步編譯時(shí)報(bào)錯,如果這個(gè)變量是靜態(tài)變量,則在定義時(shí)就必須對其賦值。
- 要確定方法的返回值類型。這里需要檢查方法的返回值類型是否確定,并檢查接受這個(gè)方法返回值的引用類型是否匹配,如果沒有返回值,則不能有任何引用類型指向方法的這個(gè)返回值。
- 所有的 Checked Exception 都要捕獲或者向上拋出。例如,我們使用 FilelnputStream讀取一個(gè)文件時(shí),必須捕獲可能拋出的
FileNotFondException
異常,或者直接向上層方法拋出這個(gè)異常。 - 所有的語句都要被執(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í)序圖:
文章來源:http://www.zghlxwxcb.cn/news/detail-487240.html
最后使用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)!