性能文章>不可以不知的Java代码编译历程>

不可以不知的Java代码编译历程原创

1年前
296833

导读

1. 编译流程主要包含哪些步?

2. javac命令主要是做什么事情的

3. 什么是JIT?

4. Java程序有哪些执行模式?

 

1、程序编译执行流程

一般情况下,一个程序从编译到执行,有以下这些阶段:

 

2、Java程序编译类型

而在Java中,有几种编译模式,如果用的是前端编译+后端编译,则把以上流程进行划分,常用的组合是: javac前端编译器+JIT后端编译器:
而在执行过程中,会进行混合模式执行: 部分函数会解释执行,部分会编译执行。

2.1、Java程序编译执行过程

如下图,为Java代码从编译到执行的过程:

 

  • 在前端编译时,把Java源文件编译为Class文件;

  • 在解释执行时,会收集运行数据,根据热点代码进行JIT编译优化,生成本地机器码,加快程序的执行。

更多关于类加载器,系统初始化,以及加载Class文件到JVM的过程,参考之前发布的两篇文章:

 

3、javac

3.1、javac中的主要类

3.2、javac主要处理流程
主要处理流程入口: JavaCompiler.compile()

 

compile2()方法中的默认编译策略:

 

  1. initProcessAnnotations(processors)

    1. 准备过程:初始化插入式注解处理器

  2. parseFiles(sourceFileObjects)解析步骤

    1. 词法分析将字符流转换为标记(Token)集合(符号流);

    2. 语法分析根据token序列构造抽象语法树,后续操作都建立在语法树上,语法分析相关类:Parser

  3. enterTrees填充符号表

  4. processAnnotations()

    1. 注解处理器的执行过程

  5. delegateCompiler.compile2()分析及字节码生成

    1. 添加实例构造器<init>()方法和类构造器<clinit>()方法;

    2. 把字符串相加操作替换为StringBuffer或者StringBuilder(JDK 1.5+);

    3. final类型的局部变量就是通过在这一步分析来保证不被重新赋值的;因为局部变量不像类变量,在Class文件中有CONSTANT_Fieldref_info符号引用,记录了访问标志。

    4. attribute语义分析过程,标注检查,主要包括诸如变量使用前是否已被声明、变量与赋值之间的数据类型是否能够匹配等;同时会进行常量折叠(int a = 1+2 折叠为 int a =3);

    5. flow语义分析过程,数据及控制流分析。这一步是对程序上下文逻辑更进一步的验证,可以检查出诸如程序局部变量在使用前是否有赋值、方法的每条路径是否都有返回值、是否所有的受检验异常都被正确处理了等问题。

    6. desugar解除语法糖

    7. generage生成字节码,同时会进行少量代码添加和转换工作。如:

      1. 添加实例构造器<init>()方法和类构造器<clinit>()方法;

      2. 把字符串相加操作替换为StringBuffer或者StringBuilder(JDK 1.5+);

 

4、Java代码编译方式和JVM三种执行模式

1、编译分类

1.1、前端编译

把Java源文件编译为Class文件的过程。 常见的前端编译器:
  • Oracle Javac

  • Eclipse JDT中的增量式编译器(ECJ)

1.1.1、优点

  • 辅助实现了Java新语法:泛型、内部类等;

  • 编译成Class文件直接给JVM解释器执行,省去编译时间,加快启动速度。

1.1.2、缺点

  • 几乎没有做任何措施优化代码的运行效率;

  • 解释器执行效率低。

1.2、后端编译

在JVM运行时,通过内置的即时编译器(Just In Time Compiler-JIT),把Class文件字节码编译为本地机器码。
常见的后端编译器:
  • HotSpot虚拟机的C1、C2编译器。

1.2.1、优点

  • 运行时收集监控信息,把热点代码编译为本地机器码,并进行各种优化,例如:

    • 运行数据分析,把堆栈操作转换为寄存器操作;

    • 消除子表达式等

  • 大大提升了执行效率。

当使用JIT编译器是,与解释执行相比,本地机器码很容易由硬件执行,将大大提高执行速度。

1.2.2、缺点

  • 收集监控信息影响程序运行;

  • 编译过程占用时间,使得启动速度变慢等;

  • 编译过程占用内存;

  • 使用较少的diam的程序无法从即时编译中收益。

1.3、静态提前编译

Ahead Of Time AOT编译
在运行期直接把Jaa源文件编译为本地机器码。

1.3.1、优点

  • 编译不占用运行时间,加快启动速度;

  • 编译本地机器码直接保存到磁盘,不占用内存。

1.3.2、缺点

  • Java语言动态性带来了额外复杂度,影响静态编译代码的质量;

  • 此方式一般不如JIT编译的质量。

2、三种执行模式

JVM中有三种执行模式: 解释执行、混合模式和编译执行,默认情况下处于混合模式。
如果想看虚拟机的执行模式,可以执行以下命令:
1
2
3
4
java -version
java version "1.8.0_71"
Java(TM) SE Runtime Environment (build 1.8.0_71-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.71-b15, mixed mode)

2.1、解释执行模式

该模式下表示全部代码均是解释执行,不做任何JIT编译,如果要开启这种模式,请使用 -Xint 参数:
1
2
3
4
java -Xint -version
java version "1.8.0_71"
Java(TM) SE Runtime Environment (build 1.8.0_71-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.71-b15, interpreted mode)
这种模式会降低运行速度,通常低10倍或者更多。

2.2、编译执行模式

该模式下不管是否热点代码,对所有的函数,都进行编译执行,如果要开启这种模式,请使用 -Xcomp 参数:
1
2
3
4
java -Xcomp -version
java version "1.8.0_71"
Java(TM) SE Runtime Environment (build 1.8.0_71-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.71-b15, compiled mode)
JVM在第一次使用时就会把所有的字节码编译为本地代码,从而优化执行速度,绕开缓慢的解释器。 但是这种模式没有让JVM启用JIT编译器的全部功能。

2.3、混合模式

JVM默认的执行模式,部分函数会解释执行,部分会编译执行。如果函数调用频率高,被反复使用,就会认为是热点代码,该函数就会被编译执行。

点赞收藏
arthinking
请先登录,查看3条精彩评论吧
快去登录吧,你将获得
  • 浏览更多精彩评论
  • 和开发者讨论交流,共同进步

为你推荐

随机一门技术分享之Netty

随机一门技术分享之Netty

从 Linux 内核角度探秘 JDK MappedByteBuffer

从 Linux 内核角度探秘 JDK MappedByteBuffer

MappedByteBuffer VS FileChannel:从内核层面对比两者的性能差异

MappedByteBuffer VS FileChannel:从内核层面对比两者的性能差异

3
3