版权声明:欢迎转载,请注明作者和出处 https://blog.csdn.net/baichoufei90/article/details/85209182
Java-JVM-javac源码笔记
摘要
本文只是简单记录下javac
的源码阅读笔记
未完待续
0x01 简介
1.1 解释执行和编译执行
可以参考文章Java-JVM-编译原理
Java程序一般是将.java文件编译为.class文件,然后再运行时由JVM的解释器(如templateInterpreter_x86_64.cpp
,bytecodeInterpreter_x86.cpp
等)解释运行字节码文件。
- 本地代码
指适合当前计算机运行的指令集
解释执行和编译执行:
- 解释执行
逐条解释执行源语言,如php
,javascript
就是典型的解释性语言。具体到java,就是用解释器直接解释执行基于栈的字节码指令集; - 编译执行
将源语言代码编译为目标代码,执行时不再需要编译,而是直接在支持目标代码的平台上运行,效率更高。具体到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