Java-底层原理-编译原理
Java-底层原理-javac源码笔记
Java-底层原理-类加载机制
Java-底层原理-clinit和init
本文只是简单记录下javac
的源码阅读笔记
未完待续
可以参考文章Java-JVM-编译原理
Java程序一般是将.java文件编译为.class文件,然后再运行时由JVM的解释器(如templateInterpreter_x86_64.cpp
,bytecodeInterpreter_x86.cpp
等)解释运行字节码文件。
解释执行和编译执行:
php
,javascript
就是典型的解释性语言。具体到java,就是用解释器直接解释执行基于栈的字节码指令集;HotSpot的解释执行和即时编译:
HotSpot的两种运行模式有不同的规定:
-server
模式:先解释字节码文件后执行。且有JIT即时编译器,会将JVM统计的执行热点代码的字节码文件编译为本地机器代码,提升执行效率。当热点不再时,就会释放这些本地代码,待执行时重新解释执行。-client
模式:逐条解释执行字节码文件。Java的编译有三类:
Javac
,此类前端编译器的优化主要是针对Java编码过程.java
转为.class
Javac
Just in time
,即时编译器。可以把热点代码直接转为机器码,提升效率。同时也是主要优化方向。对于程序运行表现至关重要。.class
转为机器码C1
(Client编译器,优化手法简单,编译时间短。针对启动性能有要求的客户端GUI程序),C2
(Server编译器,优化手段复杂,编译时间较长,运行总体性能更好。针对心梗峰值)。且在JDK1.7后,Server模式JVM采用分层编译
为默认编译策略,会根据编译器编译、优化的规模和耗时,划分不同的编译层次:Ahead of time
,静态提前编译器.java
转为机器码这里调试使用的是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();
}
主要分为
解析与填充符号表 -> 注解处理 -> 分析与字节码生成
就是前面提到过的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();
}
这里主要会进行词法和语法树解析:
语法分析使用的是com.sun.tools.javac.parser.JavacParser
Java虚拟机进阶之一:Java VM前世今生
javac、java打jar包命令实例
Javac源码简单分析之解析和填充符号表
JAVAC原理
javac 编译器原理
为啥要看javac源代码