Java编译过程(字节码编译和即时编译)
编译包括两种情况:
1,源码编译成字节码
2,字节码编译成本地机器码(符合本地系统专属的指令)
java下载过程解释执⾏也包括两种情况:
1,源码解释执⾏
2,字节码解释执⾏
解释和编译执⾏的区别是:是否产⽣中间本地机器码。
⼀、编译过程:
⼤部分的程序代码从开始编译到最终转化成物理机的⽬标代码或虚拟机能执⾏的指令集之前,都会按照如下图所⽰的各个步骤进⾏:
其中绿⾊的模块可以选择性实现。
上图中间的那条分⽀是解释执⾏的过程(即⼀条字节码⼀条字节码地解释执⾏,如JavaScript),
statics⽽下⾯的那条分⽀就是传统编译原理中从源代码到⽬标机器代码的⽣成过程。
⼆、现代经典编译原理的思路:
在执⾏前先对程序源码进⾏词法解析和语法解析处理,把源码转化为抽象语法树。
对于⼀门具体语⾔的实现来说:
词法和语法分析乃⾄后⾯的优化器和⽬标代码⽣成器都可以选择独⽴于执⾏引擎,形成⼀个完整意义的编译器去实现,这类代表是C/C++语⾔。
也可以把抽象语法树或指令流之前的步骤实现⼀个半独⽴的编译器,这类代表是Java语⾔。
⼜或者可以把这些步骤和执⾏引擎全部集中在⼀起实现,如⼤多数的JavaScript执⾏器。
三、Javac编译
在Java中提到“编译”,⾃然很容易想到Javac编译器将*.java⽂件编译成为*.class⽂件的过程,
1,这⾥的Javac编译器称为前端编译器,其他的前端编译器还有诸如Eclipse JDT中的增量式编译器ECJ等。
2,相对应的还有后端编译器,它在程序运⾏期间将字节码转变成机器码(现在的Java程序在运⾏时基本都是解释执⾏加编译执⾏),
如HotSpot虚拟机⾃带的JIT(Just In Time Compiler)编译器(分Client端和Server端)。
3,有时候还有静态提前编译器(AOT,Ahead Of Time Compiler)直接把*.java⽂件编译成本地机器代码,如GCJ、Excelsior JET等,这类编译器我们应该⽐较少
Javac编译(前端编译)的过程:
1,词法分析
词法分析是将源代码的字符流转变为标记(Token)集合。
memset函数使用详解单个字符是程序编写过程中的的最⼩元素,⽽标记则是编译过程的最⼩元素,关键字、变量名、字⾯量、运算符等都可以成为标记,
⽐如整型标志int由三个字符构成,但是它只是⼀个标记,不可拆分。
2、语法分析
语法分析是根据Token序列来构造抽象语法树的过程。
抽象语法树是⼀种⽤来描述程序代码语法结构的树形表⽰⽅式,语法树的每⼀个节点都代表着程序代码中的⼀个语法结构,如类型、修饰符、运算符等。
经过这个步骤后,编译器就基本不会再对源码⽂件进⾏操作了,后续的操作都建⽴在抽象语法树之上。
3、填充符号表
完成了语法分析和词法分析之后,下⼀步就是填充符号表的过程。
符号表是由⼀组符号地址和符号信息构成的表格。
符号表中所登记的信息在编译的不同阶段都要⽤到,在语义分析(后⾯的步骤)中,符号表所登记的内容将⽤于语义检查和产⽣中间代码,在⽬标代码⽣成阶段,
⽐如:默认构造器的添加。
帝国cms标签大全4、语义分析
语法树能表⽰⼀个结构正确的源程序的抽象,但⽆法保证源程序是符合逻辑的。
⽽语义分析的主要任务是:
对结构上正确的源程序进⾏上下⽂有关性的审查。
语义分析过程分为标注检查和数据及控制流分析两个步骤:
1,标注检查的内容包括诸如:变量使⽤前是否已被声明、变量和赋值之间的数据类型是否匹配等。
2,数据及控制流分析是对程序上下⽂逻辑更进⼀步的验证,检查出诸如:程序局部变量在使⽤前是否有赋值、⽅法的每条路径是否都有返回值、是否所有的受查异常
人民邮电异步社区5、字节码⽣成
字节码⽣成阶段不仅仅是把前⾯各个步骤所⽣成的信息转化成字节码写到磁盘中,编译器还进⾏了少量的代码添加和转换⼯作。
实例构造器<init>()⽅法和类构造器<clinit>()⽅法就是在这个阶段添加到语法树之中的
这⾥的实例构造器并不是指默认的构造函数,⽽是指我们⾃⼰重载的构造函数,
如果⽤户代码中没有提供任何构造函数,那编译器会⾃动添加⼀个没有参数、访问权限与当前类⼀致的默认构造函数,这个⼯作在填充符号
表阶段就已经完成了。
四、JIT编译
1、即时编译的产⽣:
Java程序最初是仅仅通过解释器解释执⾏的,即对字节码逐条解释执⾏,这种⽅式的执⾏速度相对会⽐较慢,尤其当某个⽅法或代码块运⾏
的特别频繁时,这种⽅式的执⾏效率就显得很低。
于是后来在虚拟机中引⼊了JIT编译器(即时编译器),
当虚拟机发现某个⽅法或代码块运⾏特别频繁时,就会把这些代码认定为“Hot Spot Code”(热点代码),
为了提⾼热点代码的执⾏效率,在运⾏时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进⾏各层次的优化,完成这项任务的正是JIT编译器。
PS:区别是:即时编译⽣成机器相关的中间码,可重复执⾏缓存效率⾼。解释执⾏直接执⾏字节码,重复执⾏需要重复解释。
现在主流的商⽤虚拟机(如Sun HotSpot、IBM J9)中⼏乎都同时包含解释器和编译器
(三⼤商⽤虚拟机之⼀的JRockit是个例外,它内部没有解释器,因此会有启动相应时间长,但它主要是⾯向服务端的应⽤,这类应⽤⼀般
不会重点关注启动时间)。
⼆者各有优势:
当程序需要迅速启动和执⾏时,解释器可以⾸先发挥作⽤,省去编译的时间,⽴即执⾏;
当程序运⾏后,随着时间的推移,编译器逐渐会失去作⽤,把越来越多的代码编译成本地代码后,可以获取更⾼的执⾏效率。
解释执⾏可以节约内存,⽽编译执⾏可以提升效率。
⽬前主流的HotSpot虚拟机中默认是采⽤解释器与其中⼀个编译器直接配合的⽅式⼯作。
2、运⾏过程中会被即时编译器编译的“热点代码”有两类:
被多次调⽤的⽅法。
被多次调⽤的循环体。
两种情况,编译器都是以整个⽅法作为编译对象,这种编译也是虚拟机中标准的编译⽅式。
⼀段代码或⽅法是不是热点代码,是不是需要触发即时编译,需要进⾏Hot Spot Detection(热点探测)。
3、⽬前主要的热点 判定⽅式有以下两种:
3.1、基于采样的热点探测:
虚拟机会周期性地检查各个线程的栈顶,如果发现某些⽅法经常出现在栈顶,那这段⽅法代码就是“热点代码”。
好处是:实现简单⾼效,还可以很容易地获取⽅法调⽤关系,
缺点是:很难精确地确认⼀个⽅法的热度,容易因为受到线程阻塞或别的外界因素的影响⽽扰乱热点探测。
3.2、基于计数器的热点探测:
虚拟机会为每个⽅法,甚⾄是代码块建⽴计数器,统计⽅法的执⾏次数,
如果执⾏次数超过⼀定的阀值,就认为它是“热点⽅法”。
这种统计⽅法实现复杂⼀些,需要为每个⽅法建⽴并维护计数器,⽽且不能直接获取到⽅法的调⽤关系,但是它的统计结果相对更加精确严谨。
4、在HotSpot虚拟机中使⽤的是第⼆种——基于计数器的热点探测⽅法的两个计数器:
⽅法调⽤计数器和回边计数器。
busstation4.1、⽅法调⽤计数器:
⽤来统计⽅法调⽤的次数,在默认设置下,⽅法调⽤计数器统计的并不是⽅法被调⽤的绝对次数,⽽是⼀个相对的执⾏频率,即⼀段时间内⽅法被调⽤的4.2、回边计数器:
⽤于统计⼀个⽅法中循环体代码执⾏的次数.
(准确地说,应该是回边的次数,因为并⾮所有的循环都是回边),
在字节码中遇到控制流向后跳转的指令就称为“回边”。
在确定虚拟机运⾏参数的前提下,这两个计数器都有⼀个确定的阀值,当计数器的值超过了阀值,就
会触发JIT编译。
5、即时编译和解释执⾏的执⾏顺序:
触发了JIT编译后,在默认设置下,执⾏引擎并不会同步等待编译请求完成,
⽽是继续进⼊解释器按照解释⽅式执⾏字节码,直到提交的请求被编译器编译完成为⽌(编译⼯作在后台线程中进⾏)。
当编译⼯作完成后,下⼀次调⽤该⽅法或代码时,就会使⽤已编译的版本。
6、⽅法调⽤计数器触发即时编译的流程:
⽅法计数器触发即时编译的过程与回边计数器触发即时编译的过程类似
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论