Java-底层原理-javac源码笔记

Java-底层原理-javac源码笔记

系列文章目录

Java-底层原理-编译原理
Java-底层原理-javac源码笔记
Java-底层原理-类加载机制
Java-底层原理-clinit和init

摘要

本文只是简单记录下javac的源码阅读笔记

未完待续

0x01 简介

1.1 解释执行和编译执行

可以参考文章Java-JVM-编译原理
Java程序一般是将.java文件编译为.class文件,然后再运行时由JVM的解释器(如templateInterpreter_x86_64.cpp,bytecodeInterpreter_x86.cpp等)解释运行字节码文件。

  • 本地代码
    指适合当前计算机运行的指令集

解释执行和编译执行:

  • 解释执行
    逐条解释执行源语言,如phpjavascript就是典型的解释性语言。具体到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源码

这里调试使用的是openjdk8javac代码在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,方法主要看compilecompile2:

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

这里主要会进行词法和语法树解析:

  1. 词法分析,CharStream拆分为tokens
  2. 语法分析,将tokens构建为抽象语法树(AST)。其每个节点代表一个语法结构,如包、运算符等。

语法分析使用的是com.sun.tools.javac.parser.JavacParser

0xFF

F.1 好文推荐

Java虚拟机进阶之一:Java VM前世今生

javac、java打jar包命令实例

Javac源码简单分析之解析和填充符号表

JAVAC原理

javac 编译器原理

为啥要看javac源代码

你可能感兴趣的:(源码,java,jvm)