Java-JVM-javac原始碼筆記
阿新 • • 發佈:2018-12-26
Java-JVM-javac原始碼筆記
摘要
本文只是簡單記錄下javac
的原始碼閱讀筆記
未完待續
0x01 簡介
1.1 解釋執行和編譯執行
可以參考文章Java-JVM-編譯原理
Java程式一般是將.java檔案編譯為.class檔案,然後再執行時由JVM的直譯器(如templateInterpreter_x86_64.cpp
,bytecodeInterpreter_x86.cpp
等)解釋執行位元組碼檔案。
- 原生代碼
指適合當前計算機執行的指令集
解釋執行和編譯執行:
- 解釋執行
逐條解釋執行源語言,如php
,javascript
- 編譯執行
將源語言程式碼編譯為目的碼,執行時不再需要編譯,而是直接在支援目的碼的平臺上執行,效率更高。具體到java,指以方法為基本單位,將位元組碼翻譯為本地機器碼後再執行。
HotSpot的解釋執行和即時編譯:
- 解釋執行
將已編譯的位元組碼檔案逐行轉換為原生代碼,並使用直譯器執行之。每次運行同樣程式碼也需要反覆轉換位元組碼到原生代碼過程。
優點:不用等待 - 即時編譯(Just In Time Compile, JIT編譯)
指以方法為基本單位,將位元組碼翻譯為原生代碼後再執行。也就是說說,初次執行時速度慢,以後執行可以直接執行原生代碼,速度快
優點:總的來說效率更高
HotSpot的兩種執行模式有不同的規定:
-server
模式:先解釋位元組碼檔案後執行。且有JIT即時編譯器,會將JVM統計的執行熱點程式碼的位元組碼檔案編譯為本地機器程式碼,提升執行效率。當熱點不再時,就會釋放這些原生代碼,待執行時重新解釋執行。-client
模式:逐條解釋執行位元組碼檔案。
Java的編譯有三類:
1.1 前端編譯器
- 簡介
如Javac
,此類前端編譯器的優化主要是針對Java編碼過程 - 功能
將.java
轉為.class
- 主流實現
Javac
1.2 JIT編譯器(後端編譯器)
- 簡介
Just in time
- 功能
將.class
轉為機器碼 - 主流實現
HotSpot之C1
(Client編譯器,優化手法簡單,編譯時間短。針對啟動效能有要求的客戶端GUI程式),C2
(Server編譯器,優化手段複雜,編譯時間較長,執行總體效能更好。針對心梗峰值)。且在JDK1.7後,Server模式JVM採用分層編譯
為預設編譯策略,會根據編譯器編譯、優化的規模和耗時,劃分不同的編譯層次:
1.0層:程式直接解釋執行,
2.1層,即C1編譯。將位元組碼編譯為本地機器程式碼,
1.3 AOT編譯器
- 簡介
Ahead of time
,靜態提前編譯器 - 功能
將.java
轉為機器碼 - 主流實現
GNU Compiler for the Java(GCJ)
0x02 Javac原始碼
這裡除錯使用的是openjdk8
,javac
程式碼在jdk8/langtools/src/share/classes/com/sun/tools/javac
之中。
main方法位於com/sun/tools/javac/Main.java
:
public static void main(String[] args) throws Exception {
System.exit(compile(args));
}
然後是到com/sun/tools/javac/main/Main.java
的以下方法:
public Result compile(String[] args) {
Context context = new Context();
JavacFileManager.preRegister(context); // can't create it until Log has been set up
// 關鍵是這一步
Result result = compile(args, context);
if (fileManager instanceof JavacFileManager) {
// A fresh context was created above, so jfm must be a JavacFileManager
((JavacFileManager)fileManager).close();
}
return result;
}
後序會達到這個Main類的下面這個方法,核心程式碼如下:
public Result compile(String[] args,
String[] classNames,
Context context,
List<JavaFileObject> fileObjects,
Iterable<? extends Processor> processors)
{
// 檢測到語法不對,就返回代表錯誤的碼Result.CMDERR
if (args.length == 0
&& (classNames == null || classNames.length == 0)
&& fileObjects.isEmpty()) {
Option.HELP.process(optionHelper, "-help");
return Result.CMDERR;
}
Collection<File> files;
// 得到要命令列中要編譯的檔案集合
files = processArgs(CommandLine.parse(args), classNames);
fileManager = context.get(JavaFileManager.class);
if (!files.isEmpty()) {
// add filenames to fileObjects
comp = JavaCompiler.instance(context);
List<JavaFileObject> otherFiles = List.nil();
JavacFileManager dfm = (JavacFileManager)fileManager;
for (JavaFileObject fo : dfm.getJavaFileObjectsFromFiles(files))
otherFiles = otherFiles.prepend(fo);
for (JavaFileObject fo : otherFiles)
fileObjects = fileObjects.prepend(fo);
}
JavaCompiler comp = null;
comp = JavaCompiler.instance(context);
comp.compile(fileObjects,
classnames.toList(),
processors);
}
接下來,會走入關鍵類com/sun/tools/javac/main/JavaCompiler.java
,方法主要看compile
和compile2
:
public void compile(List<JavaFileObject> sourceFileObjects,
List<String> classnames,
Iterable<? extends Processor> processors)
{
// 初始化插入式註解處理器
initProcessAnnotations(processors);
// These method calls must be chained to avoid memory leaks
delegateCompiler =
// 2.註解處理執行
processAnnotations(
// 1.1 parseFiles:詞法分析和語法分析
// 1.2 輸入到符號表
enterTrees(stopIfError(CompileState.PARSE, parseFiles(sourceFileObjects))),
classnames);
// 3.分析及位元組碼class檔案生成
delegateCompiler.compile2();
}
0x03 Javac編譯過程
主要分為
解析與填充符號表 -> 註解處理 -> 分析與位元組碼生成
3.1 解析與填充符號表
就是前面提到過的parseFiles
,解析語法樹的過程使用的是javac
自己的一套,可以參考這篇文章JCTree語法樹結點型別。
public List<JCCompilationUnit> parseFiles(Iterable<JavaFileObject> fileObjects) {
if (shouldStop(CompileState.PARSE))
return List.nil();
// 語法樹物件
ListBuffer<JCCompilationUnit> trees = new ListBuffer<>();
Set<JavaFileObject> filesSoFar = new HashSet<JavaFileObject>();
for (JavaFileObject fileObject : fileObjects) {
if (!filesSoFar.contains(fileObject)) {
filesSoFar.add(fileObject);
trees.append(parse(fileObject));
}
}
return trees.toList();
}
這裡主要會進行詞法和語法樹解析:
- 詞法分析,CharStream拆分為tokens
- 語法分析,將tokens構建為抽象語法樹(AST)。其每個節點代表一個語法結構,如包、運算子等。
語法分析使用的是com.sun.tools.javac.parser.JavacParser