javac源码笔记与简单的编译原理

java 程序的编译工作通常使用 IDE 或 Maven,Gradle 等工具完成,开发过程容易忽视java编译期隐藏的技术细节,深入理解 javac、编译等相关概念。

javac 是 JDK 的Java语言前端编译器工具,将满足 Java 语言规范(JLS, Java Language Specification)的 .java 源文件编译成为满足JVM规范(JVMS, Java Virtual Machine Specification)的 .class 字节码文件。

javac的知识合集 = 编译原理 + JDK( JSR-269 Pluggable Annotation Processing APIJSR 199 the Java Compiler API,语法糖,javac工作原理等)+ JLS + JVMS + bytescode

javac的知识引申 = 生成字节码编译过程,并发编程内存模型java 关键字实现字节码增强技术等。

一般而言,javac 的编译过程为:源代码 –(词法分析)–> 符号Token流 –(语法分析)–> 抽象语法树 –(注解处理器)–> 插入式注解语法树 -(语义分析)-> 完整语法树 –(生成代码)–> 字节码。是java的前端编译器。

javac由java语言编写,方便调试学习。

1 hello javac

1.1 javac 经典应用

.java 源文件:

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}

class SayHiWorld{
    public static void main(String[] args) {
        System.out.println("Hi! World!" + org.joda.time.DateTime.now().toString("E"););
    }
}

编译为 .class 类文件:

javac -help
javac -cp "lib/jodatime.jar" -d target -target 1.8 HelloWorld.java
  1. -d 指定了生成class文件的根目录,并且会根据class的包路径创建子目录。
  2. -cp JRE搜索资源文件的路径指定,默认当前路径,只会影响当前进程,覆盖CLASSPATH。

1.2 javac 实例场景

├── lib
│   └── jodatime.jar
├── resources
│   └── config.xml
├── src
│   ├── HelloWorld.java
│   └── service
│                    ├── WorldConfig.java
│                    ├── WorldService.java
│                    └── impl
│                                └── EarthServiceImpl.java
└── target

自动化编译脚本

PROJECT_DIR=/Users/sapphire/Projects/java/basic
# clean target directory
rm -rf $PROJECT_DIR/target/*
# prepare arg files
find $PROJECT_DIR/src -name "*.java">$PROJECT_DIR/target/files.txt
echo "-d $PROJECT_DIR/target" >$PROJECT_DIR/target/options.txt
# compile
javac -cp "$PROJECT_DIR/lib/*" @$PROJECT_DIR/target/options.txt @$PROJECT_DIR/target/files.txt
# copy resources to target
cp -rf $PROJECT_DIR/resources/* $PROJECT_DIR/target
# clean temp files
rm -rf $PROJECT_DIR/target/options.txt $PROJECT_DIR/target/files.txt

1.3 编译 与 javac

1.3.1 编译

编译:将便于人编写、阅读、维护的高级计算机语言写的源代码程序,翻译为计算机能解读、运行的低阶机器语言的程序的过程。负责这一过程的处理的工具叫做编译器

简单理解,编译 = 高级语言源代码 —(分析 + 翻译 + 优化)—> 低级语言机器码。

编译描述
分析语法、词法、语义
翻译java语言规范 -> jvm规范
优化改善编码风格并提高效率

编译原理中,根据编译任务不同:

字节码不是机器能识别的语言,还需要 JVM 再将字节码转换成机器码。

编译期任务代表
前期编译器.java -> .classSun Javac、Eclipse JDT 的增量式编译器
后端运行期编译器.class -> 机器码HotSpot 的 C1、C2 编译器
静态前期编译器.java -> 机器码GCJ、Excelsior JET

前端编译为字节码的好处:

  1. 解耦后端JVM编译,减少JVM工作,避免每次执行时词法、语法、语义分析。方便读取,执行速度比直接解析源代码(AOT)快。
  2. 字节码可以由Groovy,Clojure,Scala跨语言生成,供JVM调用。
  3. 字节码有版本信息,可以在编译过程在一些语言层面上擦除版本变化
  4. 字节码格式比源码紧凑、轻量,方便方便网络传输。嵌入式设备不够资源跑起完整的编译器,只需嵌入一个小巧的JVM就可以编译源码。

1.3.2 前端编译 javac

javac 是java前端编译的一种编译器实现。

🗣 Tips :

  1. Javac对代码的运行效率几乎没有优化措施,性能的优化集中在后端的即时编译器中。javac编译器实现一些“语法糖”,例如foreach语法、注解等。字节码是对程序应该如何表现的描述,JVM对Program的行为和硬件有更多了解,JIT时对字节码进行任意优化。在很多情况下,编译时优化阻碍了JIT时更重要的优化。
  2. 《深入理解JAVA虚拟机》第十、十一章 编译运行期优化

1.3.3 反编译

  • javap 将字节码转化为看得懂字节码,synchronized底层依赖了ACC_SYNCHRONIZED标记和monitorentermonitorexit两个指令来实现同步。
  • jad 不支持 Java 8 - lambda 表达式,字符串的 switch 是通过equals()hashCode()实现的。
  • IDEA插件 jclasslib 字节码查看器 view | show bytecode with jclasslib

1.3.4 后端编译 Just-In-Time Compiler

本文关注javac的编译过程,对后端编译一笔带过。

传统 JVM 通过解释字节码将其翻译成对应的机器指令执行。为了解决效率问题,小部分热点代码消耗大部分的资源,引入 JIT 技术。

  1. 进行热点探测(Hot Spot Detection)识别热点代码(Hot Spot Code)翻译成机器码后缓存。HotSpot 使用基于计数器的热点探测(Counter Based Hot Spot Detection)设计方法计数器(方法、代码块)、回计数器(for/while)统计方法的执行次数,超过阀值就认为是热点方法,触发JIT编译。
  2. HotSpot 内置 Client Compiler (C1 更好的编译速度)和Server Compiler(C2 更好的编译质量)两种JIT编译模式分层编译。
  3. 编译优化,如逃逸分析、 锁消除、 锁膨胀、 方法内联、 空值检查消除、 类型检测消除、 公共子表达式消除。

JVM 实际采用解释器和JIT混用模式 Java HotSpot(TM) 64-Bit Server VM (build 13+33, mixed mode, sharing)

2 javac 编译过程源码分析

从 Sun Javac 的源码来看,编译main方法位于com.sun.tools.javac.Main

// my code
public static void main(String[] args) {
      Main m = new Main("fx_debug");
      m.compile(new String[]{"/.../HelloWorld.java"});
}

// com.sun.tools.javac.Main
public static void main(String[] args) throws Exception {
    System.exit(compile(args));//入口
}

public Result compile(String[] args,
                          String[] classNames,
                          Context context,
                          List<JavaFileObject> fileObjects,
                          Iterable<? extends Processor> processors) {
      // ...检测javac行中命令参数不对,返回错误码 Result.CMDERR
      // ...得到编译文件集合   
    files = processArgs(CommandLine.parse(args), classNames);
    fileManager = context.get(JavaFileManager.class);
    //...实例编译器
    JavaCompiler.instance(context).compile(fileObjects,
              classnames.toList(),
              processors);
}

//com.sun.tools.javac.main.JavaCompiler
public void compile(List<JavaFileObject> sourceFileObjects,
                        List<String> classnames,
                        Iterable<? extends Processor> processors) {
        // 初始化插入式注解处理器
      initProcessAnnotations(processors);
      delegateCompiler =
          // 2.注解处理执行
          processAnnotations(
                  // 1.2 输入到符号表
                  enterTrees(stopIfError(CompileState.PARSE, 
               // 1.1 词法分析和语法分析
                        parseFiles(sourceFileObjects))), classnames);
      // 3.语义分析及字节码class文件生成
        delegateCompiler.compile2();

编译过程大致可以分为3个过程

分别对应Token流,语法树,注解语法树,字节码输出。

2.1 Parse and Enter

两个重要的接口与实现

接口作用实现
com.sun.tools.javac.parser.Lexer词法分析com.sun.tools.javac.parser.JavacParser
com.sun.tools.javac.parser.Parser构建抽象语法树com.sun.tools.javac.parser.Scanner

该阶段将源码文件解析构建抽象语法树( Abstract Syntax Tree,AST )。从功能上分为 词法分析 和 语法分析,实际上同时进行。编译时,通过 ParserFactory 与 ScannerFactory 工厂类管理 JavacParser 与 Scanner 对象。JavacParser 解析时,Scanner 读取源文件字符流,逐个读入 Token,构建抽象语法树。之后,编译器就基本不会再对源码文件进行操作,后续操作都建立在抽象语法树之上。(添加默认无参构造方法等)

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

2.1.1 词法分析

将Java源代码按照Java关键字、自定义关键字、符号等按顺序分解为了可识别的Token流。

输入输出描述
源代码的字符流标记(Token)集合关键字、变量名、字面量、运算符

字符char是程序编写过程中的的最小元素,标记Token是编译过程的最小元素。

主要实现类功能
com.sun.tools.javac.parser.JavacParser规定哪些词符合Java语言规范,具体读取和归类不同词法的操作由scanner完成。
com.sun.tools.javac.parser.Scanner负责逐个读取源代码的单个字符,然后解析符合Java语言规范的Token序列,调用一次nextToken()都构造一个Token
com.sun.tools.javac.parser.Tokens$Token规定了所有Java语言的合法关键词,包含了开始/结束位置,类型。
com.sun.tools.javac.parser.Tokens$TokenKind描述一个Token的类型,如IDENTIFIER(自定义标识)、BOOLEAN、BREAK、BYTE、CASE。
com.sun.tools.javac.util.Names用来存储和表示解析后的词法,每个字符集合都会是一个Name对象,所有的对象都存储在Name.Table内部类中。
com.sun.tools.javac.parser.KeyWords *负责将字符集合对应到token集合中。JDK9后由Tokens完成

🗣 Tips :

  1. java命名规范指出声明变量的时候必须以字母、下划线或者美元符开头,包括字母、数字、下划线或者美元符。由JavacParser规定并识别 int y=x+1; package per.rsf.javac; 的Token流。
  2. Tokens根据Token.name先转化成Name对象,建立Name和Token的对应关系,保存在key数组中。这个key数组只保存了在Token类中定义的所有Token到Name对象的关系,而其他所有字符集合Tokens都会将它对应到TokenKind.IDENTIFIER类型
  3. Javac中每个与文件相关的实现类都直接或间接实现了JavaFileObject接口,这个接口专门为操作.java文件及.class文件而定义的。每个RegularFileObject类对象可以代表一个Java源文件。调用getCharContent()方法获取字符流输入。
  4. 关键代码nextToken的主要逻辑:处理特殊字符、标识符、16进制、数字、分隔符、斜杠开头、反斜杠开头、双引号开头、默认处理。

例:package的Token读取流程

2.1.2 语法分析

输入输出描述
标记(Token)集合抽象语法树(AST)包、类型、运算符、修饰符、接口、返回值、代码注释

将Token流组装成更结构化的语法树,描述程序代码语法结构,检查是否符合Java语言规范。每个语法树上的节点都是com.sun.tools.javac.tree.JCTree的一个实例,继承自com.sun.source.tree.Tree接口,代表着程序代码中的一个语法结构(Construct),如包、类型、修饰符、运算符、接口、返回值甚至代码注释。

实现类功能
com.sun.tools.javac.tree.TreeMaker生成语法节点,根据Name对象构建一个语法节点
com.sun.tools.javac.tree.JCTree生成的语法节点都会继承jctree和实现(如根节点JCCompilationUnit)
com.sun.tools.javac.tree.JCTree#Tagenum类,区分语法树的类型。类型的数值是上一个节点类型的数值+1
com.sun.tools.javac.tree.JCTree#pos语法节点在源文件中的起始位置,-1表示不存在
com.sun.tools.javac.tree.JCTree#typeJava类型(int、float、String)

🗣 Tips :

  1. JCCompilationUnit表示一个编译单元,一般是一个源文件(可以是多个类)内容对应一个编译单元,同时这也是顶层的树节点。包含包注解 List、包名 JCExpression、和树 List。

  2. 在遍历像抽象语法树这样由各种类的实例所组成的树形结构时,通常会借助Visitors访问者模式来完成。

2.1.3 填充符号表

该步骤实际发生在语义分析中,在处理注解前。

一个类中的符号变量,除了类本身定义,其他类定义。如调用其他类方法、变量,继承或实现父类和接口等。

调用其他类的符号变量时,就需要通过符号表来进行查找。(符号引用)

这些类的符号也需解析到符号表中。com.sun.tools.javac.comp.Enter按照递归向下的顺序解析语法树,

  1. 将所有类中出现的符号输入到自身的符号表,并将类符号、类的参数类型符号(泛型参数类型)、超类符号,继承类型符号和继承的接口类型符号都存储到一个未处理列表中。
  2. 将这个未处理列表中的所有类都解析到各自的符号列表中。

输入输出描述
当前范围的定义域(definitions)待处理列表,包含需要分析并生成类文件的树.一组符号地址和符号信息构成的表格

符号表是由一组符号地址和符号信息构成的表格。在语义分析中,符号表所登记的内容将用于语义检查和产生中间代码。在目标生成阶段,当对符号名进行地址分配时,符号表是地址分配的依据。

实现类功能
com.sun.tools.javac.comp.Enter内容填充,符号表(Symbol Table)填充的出口是一个待处理列表,包含了每一个编译单元的抽象语法树的顶级节点以及 package-info.java 的顶级节点。
com.sun.tools.javac.comp.MemberEnter使类变得完整,确定类的泛型参数、父类、接口,该类的所有符号输入到它所对应的scope
VarSymbol预定义符号的输入,对操作符的处理

2.2 Annotation Processing

JDK 6 实现了插入式注解处理API(JSR-269),位于javax.annotation.processingjavax.lang.model包。

通过声明一个注解,实现一个注解处理器。注册服务后

在编译期由com.sun.tools.javac.processing.JavacProcessingEnvironment处理注解。读取、修改、添加抽象语法树中的任意元素。像反射一样访问类、字段、方法和注解等元素,创建新的源文件。每一个插入式注解处理器操作语法树,编译器将回到解析及填充符号表的过程循环。

作用:减少编写配置文件的劳动量,提高代码可读性。

测试使用时,测试类和实现类写在不同子模块下!!!否则编译不通过!!!一般需要插件工具完成,如 Lombok 插件

2.2.1 javac 命令编译过程

  1. 声明一个注解
  2. 创建一个注解处理器,注解处理器需实现 javax.annotation.processing.Processor 接口或继承 javax.annotation.processing.AbstractProcessor 类。重写process方法。
  3. 为注解处理器注册服务。在META-INF.services文件夹下创建 javax.annotation.processing.Processor文件。写入注解处理器的全称。
  4. 为了能够让项目能够通过编译,我们需要为Java编译器添加一个不进行注解处理的参数
 <!--maven默认运行javac编译,将javax.annotation.processing.Processor文件作为类路径的一部分。此时编译器期望注释处理器已编译成实例。引发“错误的服务配置文件...”错误。-proc:none 禁用注释处理--> 
    <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-compiler-plugin</artifactId>
           <configuration>
                   <compilerArgument>-proc:none</compilerArgument>
           </configuration>
   </plugin>

也可使用 javac -processor指定注解处理器

主要源码:

// 注解处理器
// 以下注解可以重写get...方法 如getSupportedAnnotationTypes getSupportedSourceVersion
// @SupportedAnnotationTypes("...")//注册注解处理器要处理的注解类型
// @SupportedSourceVersion(SourceVersion.RELEASE_8) 注册注解处理器要处理的源代码版本
// @SupportedOptions 注册通过命令行传递给处理器的操作选项
public MyProcessor extends AbstractProcessor{
    @Override
      // 自动调用,ProcessingEnvironment类提供工具类:Filer,Types,Elements,Messager等
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
    }

        @Override
      // 处理器的main()方法。自动调用,扫描和处理注解的主要逻辑,RoundEnvironment类用于查找出程序元素上使用的注解
      // 返回值true:接下来的处理器不可处理该注解;false:接下来的处理器可以处理该处理器处理的注解。
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        return false;
    } 
}

public interface ProcessingEnvironment {
    Map<String,String> getOptions();
    //Messager用来报告错误,警告和其他提示信息
    Messager getMessager();
    //Filer用来创建新的源文件,class文件以及辅助文件
    Filer getFiler();
    //Elements中包含用于操作Element的工具方法
    Elements getElementUtils();
    //Types中包含用于操作TypeMirror的工具方法
    Types getTypeUtils();
    SourceVersion getSourceVersion();
    Locale getLocale();
}

public interface RoundEnvironment {
        // 是否最终轮
    boolean processingOver();
      // 上一轮注解处理器是否产生错误
    boolean errorRaised();
      // 返回上一轮注解处理器生成的根元素
    Set<? extends Element> getRootElements();
      // 返回包含指定注解类型的元素的集合
    Set<? extends Element> getElementsAnnotatedWith(TypeElement a);
    // 返回包含指定注解类型的元素的集合
    Set<? extends Element> getElementsAnnotatedWith(Class<? extends Annotation> a);
}
元素含义
Element程序元素(源代码)
VariableElement代表一个字段,枚举常量,方法或者构造方法的参数,局部变量及异常参数等元素
PackageElement包元素
TypeElement类或接口元素
ExecutableElement代码方法,构造函数,类或接口的初始化代码块等元素,也包括注解类型元素
TypeMirror声明类型(类类型和接口类型),数组,类型变量和空类型。也代表通配类型参数,可执行文件的签名和返回类型等。TypeMirror = Element.asType()
DeclaredType声明类型:类类型还是接口类型

2.2.2 编译器 API 实例过程

使用 CompilationTask 的 setProcessors 方法可以传入注解处理器。

JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
VisitProcessor processor = new VisitProcessor();

StandardJavaFileManager manager = compiler.getStandardFileManager(diagnostics, null, null);
File file = new File(".../VisitProcessor.java");
Iterable < ?  extends JavaFileObject> sources = manager.getJavaFileObjectsFromFiles(Arrays.asList(file));

CompilationTask task = compiler.getTask(null, manager, diagnostics, Arrays.asList("-d", "target/classes"), null, sources);
task.setProcessors(Arrays.asList(processor));
task.call();

manager.close();

2.2.3 实例

仿照findbugs实现一个简单的类编写规范检查

注解类 CHECK
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface Check {//标识一个类被 findbugs 检查
}
注解处理类 CLASSCHECKER
//以下两个注解可以重写 getSupportedAnnotationTypes 与 getSupportedSourceVersion 方法
@SupportedAnnotationTypes("per.rsf.jsr269.anno.Check")//注册注解处理器要处理的注解类型
@SupportedSourceVersion(SourceVersion.RELEASE_8)//注册注解处理器要处理的源代码版本
//@SupportedOptions 注册通过命令行传递给处理器的操作选项
public class ClassChecker extends AbstractProcessor {
   @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        Set<? extends Element> elements = roundEnv.getRootElements();
        ClassScanner scanner8 = new ClassScanner();
        for (Element element : elements) {
            scanner8.scan(element);
        }
        return false;
    } 
}

class ClassScanner extends ElementScanner8<Set<? extends Element>, Element> {
      //字段
        @Override
    public Set<? extends Element> visitVariable(VariableElement e, Element element) {
          // if e.getConstantValue() != null 访问静态常量
          // if e.getEnclosingElement().getKind() == ElementKind.ENUM 访问枚举
          // else 访问实例变量
          // ...
          return super.visitVariable(e, element);
    }
        //类
    @Override
    public Set<? extends Element> visitType(TypeElement e, Element element) {
          // e.getQualifiedName() 访问全类名
          // e.getSimpleName() 访问类名
          // ...
        return super.visitType(e, element);
    }
        //方法
    @Override
    public Set<? extends Element> visitExecutable(ExecutableElement e, Element element){
        // e.getReturnType().getKind() == TypeKind.BOOLEAN 访问方法返回类型
          // ...
        return super.visitExecutable(e, element);
    }
}
注册服务

在文件夹resources/META-INF/services下创建文件javax.annotation.processing.Processor,内容如下:

per.rsf.jsr269.processor.ClassChecker
测试类
@Check
public class testCHECK {
    static int a;
    String UPPER;

    public boolean haveIS(){
        return true;
    }
}
结果

将项目打成jar包使用

javac -cp /.../javac/target/javac.jar testCHECK.java

🗣 Tips :

  1. 模拟Lombok实现get set方法,依赖 com.sun.tools.javac.* 包

2.3 Analyse and Generate

2.3.1 语义分析

输入输出描述
语法树字节码文件标注检查,数据及控制流分析

语法分析将源文件抽象成结构正确的抽象语法树,但无法保证符合逻辑的。还需添加默认的构造器,检查变量使用前是否已经初始化等。

实现类功能
com.sun.tools.javac.comp.Attr标注检查:名称消解、变量使用声明、类型检查、常量折叠、推导泛型方法的参数类型
com.sun.tools.javac.comp.Flow数据及控制流分析:局部变量在使用前是否被正确赋值、final变量不被重复修饰、确定方法返回值类型、方法的每条路径是否都有返回值、是否所有的受查异常都被正确处理
com.sun.tools.javac.comp.Check用来辅助Attr类检查语法树中变量类型是否正确,如二元操作符两边的操作数的类型是否匹配,方法返回值是否和接收的引用值类型匹配
com.sun.tools.javac.comp.Resolve检查变量,方法或者类的访问是否合法,变量是否是静态变量
com.sun.tools.javac.comp.ConstFold将一个字符串常量中的多个字符合并成一个字符串
com.sun.tools.javac.comp.Infer帮助推导泛型方法的参数类型

🗣 Tips :

  1. 常量折叠:a=1+2 -> a=3
  2. 如果代码中没有提供任何构造函数,自动添加一个没有参数、访问权限与当前类一致的默认构造函数。如果提供了构造函数,则在代码生成阶段添加。
语法糖(SYNTACTIC SUGAR)

Java中最常用的 语法糖 主要是前面提到过的泛型、变长参数、自动装箱/拆箱等。JVM运行时不支持这些语法,它们在编译阶段还原回简单的基础语法结构,这个过程称为解语法糖。解语法糖的过程由JavaCompiler#desugar()方法触发。

实现类功能
com.sun.tools.javac.comp.TransTypesGeneric Java to conventional Java
com.sun.tools.javac.comp.Lowerinner classes, class literals, assertions, foreach loops, etc.

2.3.2 字节码生成

输入输出描述
语法树、符号表字节码文件编译器添加和转换少量的代码,生成字节码文件。

把前面各个步骤所生成的信息转化成字节码,

  1. 代码收敛,将方法块转成符合JVM语法的命令形式,jvm的所有操作都是基于栈的,所有操作都必须经过进出栈来完成。
  2. 按照jvm的文件组织格式将字节码输出到以class文扩展名的文件中。

在收敛代码的过程中,将实例构造器方法和类重载构造器方法添加到语法树之中。

实现类功能
com.sun.tools.javac.jvm.Gen遍历语法树,生成JVM操作码序列结合
om.sun.tools.javac.jvm.Items表示任何可寻址的操作项,这些操作项都可以作为一个单位出现在操作栈上,不同的Item对应不同JVM操作码
com.sun.tools.javac.jvm.Code存储生成的字节码,并提供一些能够映射操作码的方法
com.sun.tools.javac.jvm.ClassWriter输出字节码,生成最终的Class文件
CLINITINIT

方法编译器自动收集static代码块,收敛顺序为先父后子,先单后块,父接口“随遇而安”。final static 静态不可变常量提前被编译器放入常量池,无需初始化。

方法编译器自动收集非static代码块,收敛顺序为先父后子,先单后块最后构造函数

方法(实例化阶段)永远在方法(类初始化阶段)执行后执行。🗣 Tips :

  1. clinit类构造器,在jvm进行类加载—验证—解析—初始化中的初始化阶段(类实例化 调用静态字段或方法)调用,对静态变量,静态代码块进行初始化。多线程时,clinit方法阻塞,一个类只会在一个JVM进程运行期间执行一次clinit方法。
  2. init实例构造器,类实例化阶段(new 反射 克隆 反序列化),对非静态变量解析初始化。若实例时类没有初始化,先执行clinit方法。
static class Parent{
      public int C=1;//init
    public static int A=1;//clinit
    static{ A=2;}//clinit
    static class Sub extends Parent{ 
           public int C=2;//init
          public static int B=A//clinit
    }
    public static void main(String[]args){
            System.out.println((new Sub).C);//2
            System.out.println(Sub.B);//2
    }
}

3 javac API


JDK 6 增加了规范 JSR-199 和 JSR-296,提供相关的 API

绿色标注的包是官方 API(Official API), JSR-199 和 JSR-296.

黄色标注的包为(Supported API).

紫色标注的包代码全部在 com.sun.tools.javac.* 包下,为内部 API(Internal API)和编译器的实现类。

类名注释
javax.annotation.processing注解处理 (JSR-296)
javax.lang.model注解处理和编译器 Tree API 使用的语言模型 (JSR-296)
javax.lang.model.element语言元素
javax.lang.model.type类型
javax.lang.model.util语言模型工具
javax.toolsJava 编译器 API (JSR-199)
com.sun.source.*编译器 Tree API,提供 javac 工具使用的抽象语法树 AST 的只读访问
com.sun.tools.javac.*内部 API 和编译器的实现类

4 javac 调试

4.1 修改 idea.vmoptions

-Dcompiler.process.debug.port=12346

4.2 配置远程debug

4.3 启用idea编译调试

IDEA默认会禁用编译调试,这里需要开启一个开关,以此让IDEA在编译之前等待调试程序的链接。并且,这个配置在IDEA重启后会失效

双击Shift,打开平常搜索类的界面,输入debug build process

本文作者: Sapphire
本文链接: https:rensifei.site/2017/03/javac/
版权声明: 转载请注明出处!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值