In-depth analysis of the Java compiler and runtime

I wonder if you have not thought about when we use the IDE to write a Demo class, and perform main function prints hello world have gone through what processes it?
To think through this article Java execution flow analysis, or in other words you want to talk to compile Java processes running period.

  • Straight to the point
  • During compilation of what you did
  • During the operation have done what

1. straight to the point

public class MyApp {

    public static void main(String[] args) {
        System.out.println("hello world");
    }
}

If we wrote a MyApp.java, and you want to print 'hello world' What are the steps that it needs to go through?

The first step : the compile

Compiled by a compiler, from Java source ---> Java bytecode

This compiler is in the jdk javac compiler, we need javac MyApp.java which can compile the source code, javac compiler located jdk -> bin -> javac

Step two : the Load and the Execute

Loading and executing java bytecode

Loaded with can be run in the jdk java command java byte code, we just java MyApp.class to load and execute the bytecode, when running java command, JRE will specify your class. Then, the main method of the class.

java command located jdk -> bin -> java.

The above are just spoke about running a java program process, and then from the perspective of the following compile and run the analysis at the details.

2. During the compilation have done what?

Compiler (Compiler) is a computer program, it will be written in a programming language source code (in the original language) into another programming language (target language).

Compile what has been done? From our point of view of the user byte code is nothing more than the source code can be compiled into virtual machine execution, but from the perspective of the platform (compiler), it has a lot of experience in the process.
After all, we can not give you anything with the suffix .java files to compile it, you need to check a variety of analytical steps

2.1 Analysis and filling symbol table

The syntax lexical analysis

词法分析

是指把源代码的字符流转为标记(Token)集合,标记(Token)是编译阶段的最小单元,字符则是编程阶段源码的最小单元。
比如,int i = 0由4个标记构成分别是「int,i,=,0」编译器只认识这些标记,词法分析过程就是识别一个个标记的过程

语法分析

则是把生成的标记集合 构成一个语法树,每个节点代表程序代码中的语法结构,如包,类型,修饰符,运算符等等。

填充符号表
通过了上面的词义语义分析之后我们需要把数据存起来,以供后续流程使用,编译器会以key-value的形式存储数据,以符号地址为key符号信息为value,具体形式没做限制可以是树状符号表或者有序符号表等。
在语义分析中,根据符号表所登记的内容 语义检查和产生中间代码,在目标代码生成阶段,当对符号表进行地址分配时,该符号表是检查的依据。

2.2 注解处理器

注解与普通的Java代码一样,是在运行期间发挥作用的。我们可以把它看做是一组编译器的插件,在这些插件里面,可以读取、修改、添加抽象语法树中的任意元素。
如果这些插件在处理注解期间对语法树进行了修改,编译器将回到解析及填充符号表的过程重新处理,直到所有插入式注解处理器都没有再对语法树进行修改为止。
换句话说当我们处理注解时如果修改了语法树的话会重新执行分析以及符号填充过程,把注解也填充进来,直到处理完所有注解。

2.3 语义分析

语法分析以及处理注解之后,编译器获得了程序代码的抽象语法树,语法树能表示一个结构正确的源程序的抽象,但无法保证源程序是符合逻辑的
说白了,语法树上的内容单个来说是合法的但是结合到上下文语义则未必是合法的。

比如定义了两个变量
int a = 1; boolean b = false; int c = a + b
以上 都能构成结构正确的语法树,但是根据语义分析之后编译是通不过,Java语言中是不合乎逻辑的。

2.4 解语法糖

Java 中最常用的语法糖主要有泛型、变长参数、条件编译、自动拆装箱、内部类等。虚拟机并不支持这些语法,它们在编译阶段就被还原回了简单的基础语法结构,这个过程成为解语法糖。

换句话说,不论你是否使用Java的语法糖,最终到jvm哪里的时候都是一样的,jvm不支持语法糖,所以需要编译阶段解语法糖,语法糖的初衷是用来提升开发效率,而不是代码性能。

2.5 字节码生成

字节码生成是Javac编译过程的最后一个阶段,在Javac源码里面由com.sun.tools.javac. jvm.Gen类来完成。字节码生成阶段前面各个步骤所生成的信息(语法树、符号表)转化成字节码写到磁盘中,主要工作就是把语法树和符号表加工成字节码文件。

3. 运行期间都做了什么?

java的运行期主要是处理编译器产生的字节码,包括加载与执行

3.1 加载器与验证器

java提供类加载器把虚拟机外部的字节码资源载入到虚拟机的运行时环境(主要是指虚拟机的方法区)
并提供字节码验证器来保证载入的字节码是安全合法的,对程序没有危害的。

加载器 (Class Loader)

当字节码还没被类加载器加载之前它目前还处于虚拟机外部存储空间里,要想执行它需要通过类加载器来加载到虚拟机的运行时内存空间里。关于类加载器不太想过多扩展,有兴趣珂查阅相关书籍资料。

常见类加载器有:

  • Bootstrap ClassLoader(启动类加载器:加载位于 \lib 目录下的类文件,如rt.jar
  • Extension ClassLoader(扩展类加载器): 加载位于 \lib\ext目录下的类文件
  • Application ClassLoader(应用程序类加载器):加载位于类路径(ClassPath)下的类文件

总之,加载器的任务就是把字节码资源载入到虚拟机运行时环境里

字节码验证 (Bytecode Verifier)

当类加载器将新加载的字节码呈现给虚拟机时,首先由验证器来检查验证这些字节码。验证程序检查指令是否无法执行明显有害的操作。除系统类之外的所有类都需要经过验证。也可以使用命令-noverify选项来停用验证。

字节码验证器主要验证如下几项:

  • 变量在使用前初始化
  • 不违反访问私有数据和方法的规则
  • 运行时堆栈不会溢出
  • 所有Java虚拟机指令的参数都是有效类型
  • 各种类型检查

参考 http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.10

总之,验证器的任务就是保证加载器载入的字节码资源的安全性,正确性

3.2 解释器与JIT编译器

解释器

解释器(interpreter),是一种计算机程序,能够把高级编程语言一行一行解释 运行

划重点:一行一行运行,说白了就是效率低

解释器每次运行程序时都要一行一行先转成另一种语言再作运行,因此解释器的程序运行速度比较缓慢。它不会一次把整段代码翻译出来,而是每翻译一行程序叙述就立刻运行,然后再翻译下一行,再运行,如此不停地进行下去。

JIT编译器

即时编译(Just-in-time compilation)是一种提高程序运行效率的方法。通常,程序在执行前全部被翻译为机器码。

Java最初的版本没有JIT编译器,完全靠解释器来运行的,但是为了提升性能便引入了JIT编译器,

重点说明:当我们说编译的时候基本上指的是上面的从源码到字节码的编译过程,而不是指JIT编译器

JIT编译器工作阶段基本是java程序运行期的最后阶段了,它的工作是将加载的字节码转换为机器码。当使用JIT编译器时,硬件可以执行JIT编译器生成的机器码,而不是让JVM重复解释执行相同的字节码导致相对冗长的翻译过程。 这样可以带来执行速度的性能提升。

什么时候触发即时编译?

  • 被多次调用的方法
  • 被多次执行的循环体

上面两个条件又叫做热点代码,至于如何界定这个多次或者热点,Java提供了两种策略:

热点探测: 虚拟机定期检查线程的栈顶,如果某个方法经常出现在栈顶 则推断为热点代码

计数器: 统计方法的调用次数,维护一个计数器列表

基于计数器来推断热点代码是HotSpot虚拟机采用的策略

通常情况下,解释器和JIT编译器混合配合工作,而不是单独工作,这样可以做到互补提升整体性能。HotSpot 虚拟机的解释器JIT编译器架构如下图所示:

HotSpot虚拟机中内置了两个即时编译器,分别称为Client Compiler和Server Compiler,或者简称为C1编译器和C2编译器,默认采用解释器与其中一个编译器直接配合的方式工作,程序使用哪个编译器,取决于虚拟机运行的模式,用户也可以使用“-client”或“-server”参数去强制指定虚拟机运行在Client模式或Server模式。

4. 总结

java 程序是如何运行的?

首先需要把源代码(高级语言) 编译成虚拟机可执行的语言(字节码)
其次,需要把字节码解释运行后者编译成操作系统级别的机器语言,用于执行函数调用(System call)

Java是如何做到平台独立的?

主要是因为字节码技术。我们可以把在Windows系统上编译生成的字节码文件放在Linux系统上去执行,反之亦可。
虚拟机不在乎你是那个操作系统生成的字节码文件,他只在乎加载的这个.class字节码文件是否是正确的,安全的。

虽然Java语言是平台独立的,但是虚拟机不行。每种操作系统都要下载对应的虚拟机,这主要是由于它最终调用的函数库以及线程模型不同。

参考:

1.http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.10.
2.深入理解Java虚拟机

Guess you like

Origin www.cnblogs.com/wyc1994666/p/11366802.html