介绍

java 作为静态语言十分特殊,他需要编译,但并不是在执行之前就编译为本地机器码。

所以,在谈到 java的编译机制的时候,其实应该按时期,分为两个部分。一个是 javac指令 将java源码变为 java字节码的静态编译过程。 另一个是 java字节码编译为 本地机器码的过程,并且因为这个过程是在程序运行时期完成的所以称之为即时编译。

静态编译过程,通过javac 完成,而即时编译是通过虚拟机来完成的,即时编译机制,被内嵌于 java字节码执行引擎之中,可以算的上是 jvm的一个内存组件。

jvm的执行引擎中 有 一个解释器用来识别字节码指令,并将字节码指令映射为机器指令 调用操作系统来完成程序的运行。 这样来看,虽然实现了 java的跨平台特性,但是 却以牺牲了极大的的性能为代价。 为了提高java程序的性能,jvm实现了 即时编译机制。即,在程序运行期间,根据对热点字节码的探测(运行次数超过某个阀值的代码),将这部分热点代码进行特别的优化,将其直接编译为本地机器码执行。 这个过程由java字节码执行引擎中的 两个编译器完成,C1与C2编辑器,一个用于客户端,一个用于服务器。 c1相比较与c2他的编译优化程度要低一些,c2将针对服务器进行一些激进的优化,以保证代码在服务器运行时性能更加突出。

分层编译:

    现代虚拟机实现中,制定了多种不同的编译级别以达到适应多种开发场景的目的。  即时编译机制本身也需要占用用户内存。造成一定的内存开销,在一些场景下可能会造成较高的延迟。

同样,对于服务器端程序来说会长久的运行,花销一定的编译时间可以换来之后更高的性能,所以直接全部编译可能效果更佳。

而还存在一些 很久都不会使用一次的代码,编译这些代码就显得浪费时间。

为了解决上述的问题, 现代虚拟机,提出了 分层编译策略,类似于 分代垃圾回收机制,一种根据不同时期场景调整编译级别的优化策略。

分层编译分为  三层:

一层:   仅进行解释执行,c1与c2编译器被禁用。 这时不存在即时编译情况。

二层:  仅c1编译器运行,c1编译器是客户端编译器,仅会进行一些常规的 编译优化机制。使用大多数情况。

三层:  混合编译  c1与c2同时使用,c2编译器是服务端编译器,可以对代码进行 高性能的 激进优化,同样设定逃生门,在一些特殊情况下,激进优化后的代码并不能有更高的性能。需要进行优化回退,将重新对代码进行解释执行。

对于分层编译来说代码的编译优化级别是可以提升的,也可以使用 jvm参数进行控制。

 即时编译的基本流程:

     方法调用栈上运行着 方法栈帧,即时编译的流程从这里开始:

字节码开始是解释执行的,解释字节码的任务由解释器完成,但真实操作的是内存中的 方法栈中栈帧内的操作数栈与局部变量表。所以 java程序解释执行时运行速度相对较慢。

java程序的执行伴随着 栈帧的弹栈出栈(方法调用)以及pc寄存器的顺序执行及跳转。 即时编译的第一步就是要 探测 热点代码.

使用 热点探测技术 来统计 热点代码。  热点探测技术 实质就是 统计 某段代码频繁调用的次数,一旦超过指定的阈值就会触发即时编译。

触发条件: 热点探测

jvm 通过统计 每个方法调用栈的栈顶 一个方法栈帧的弹出频率 来作为一个指标。有两种方法,第一使用精确的计数器进行精确计数,超过阈值触发编译。二是记录一段时间内方法调用次数(方法调用的频  率) 超过阈值触发编译。并存在热度衰减,超过一定时间范围没有继续调用 该方法则会 将其值减半。  二者各有优缺点,前者 精确计算开销大,后者不够严谨但适用大多数情况。

一旦超过阈值将触发方法级别的即时编译,以整个方法为编译对象。

还存在 循环体级别的热点探测,适用回边计数器来进行计数,pc寄存器向后跳转一次记为 一个回边。 当每次跳转时,都会触发计数器加一,并将计数器的值与该循环体所在方法的频率计数器的值相加。

其值超过阈值就会触发即时编译,若没超过阈值并不存在半衰,继续以解释形式执行代码。

循环体级别的探测,也是会将整个方法进行编译的。

即时编译:

一旦判定代码段是热点代码,则解释器将发送一次请求编译器,进行编译,在编译成功之前 解释器仍旧运行着。 等编译完成后,直接将pc寄存器中方法的调用地址进行替换,替换为编译后的方法地址。

这一过程就是 栈上替换---OSR.

  编译优化:

    javac只能进行一些 静态优化,优化上存在一些局限性。而在jvm中即时编译过程中进行的优化,是一种动态编译优化。

即时编译器会进行很多优化,介绍几种比较 经典的优化。

公共子表达式的消除:

在一个表达式中 有一部门表达式被计算过,并且在之后的代码中出现了同样的表达式并且表达式的值没有发生改变。那么编译器就会将 这部分表达式用计算结果进行替换。以避免重复计算造成的时间开销。

方法内联:

c/c++这种静态编译的语言,实现方法内联是很简单的,但java作为动态编译语言,方法内联存在不确定性。

在编译时,将方法调用 直接使用 方法体中的代码进行替换,这就是方法内联,这样做,减少了 方法调用过程中 压栈与入栈的开销。同时 为之后的一些优化手段提供条件。

对非虚方法进行内联是容易的,但对虚方法而言就比较复杂了,需要禁用 运行时类型继承分析机制 来确定虚方法的实际调用者。 因为多态机制的存在,方法的调用者仅在运行时期才能知晓。并且会发生改变。 这就要求对虚方法的内联必须存在 逃生门,可以在 方法调用者,也就是继承关系发生变化时 取消内联。

逃逸分析:

如果一个变量的使用,在运行期检测 他的作用范围不会超过一个方法或者一个线程的作用域。那么这个变量就不会被多个线程所共享,也就是说 可以不将其分配在堆空间中,而是将其线程私有化。

那么 如何来检测一个变量的作用域仅在 一个方法或者线程中呢? jvm中使用 数据流分析机制 实现的一种机制。 称之为 逃逸分析,作为其他一些激进优化的前提判断条件。

栈上分配:

如果一个变量经过逃逸分析后,判定可以被线程私有的,那么jvm将进行 一个大胆的优化手段, 栈上分配。 java 仅允许在 堆空间创建对象,但jvm的发展已经打破了这一规定。 如果一个对象,注定是线程私有的 那么为什么要放在堆空间,GC的回收以及主存与工作内存的同步都需要消耗大量资源。 而放在栈空间则不在需要担忧这些,对象将跟随栈的创建而创建,销毁而销毁。

标量替换:

标量,指的是 jvm中描述数据最基本的单位。 列如 原始数据类型等。

    当确定一个对象不会逃逸后,那么就要分配他到栈空间上,然而栈空间是有限的,为了进一节省栈空间,就需要将 对象(聚合量) 拆散为标量。 这样 在jvm不会在栈中创建 对象而是仅仅创建对象的成员变量。

这样就节省了空间,因为没有对象头以及对齐填充的空间浪费。

同步消除:

同样基于 逃逸分析,当加锁的变量不会发生逃逸,是线程私有的那么,完全没有必要加锁。 在jit编译时期就可以将同步锁去掉,以减少 加锁与解锁造成的资源开销。

    

   

    

JIT——即时编译的原理的更多相关文章

  1. 浅析 JIT 即时编译技术

    即时编译回顾 HotSpot 虚拟机执行 Java 程序时,先通过解释器对代码解释执行,发现某个方法或代码块执行比较频繁后,对热点代码进行编译,编译后生成与本地平台相关的机器码,再去执行机器码获得较高 ...

  2. java 笔记(1)-—— JVM基础,内存数据,内存释放,垃圾回收,即时编译技术JIT,高精度类型

    1.java中5个存放数据的地方: (1).寄存器(Registers):位于CPU内部,是速度最快的存储区,但是数量和容量有限.在java中不能直接操作寄存器. (2).栈(Stack):栈位于通用 ...

  3. JVM即时编译(JIT)

    Java解释执行过程: 代码装入-代码校验-代码执行 Java字节码的执行方式分为两种:即使编译方式和解释执行方式.即时编译是值解释器先将字节码编译成机器码,然后执行该机器码.解释执行的方式是指解释器 ...

  4. Java 面试-即时编译( JIT )

    当我们在写代码时,一个方法内部的行数自然是越少越好,这样逻辑清晰.方便阅读,其实好处远不止如此,通过即时编译,甚至可以提高执行时的性能,今天就让我们好好来了解一下其中的原理. 简介 当 JVM 的初始 ...

  5. JIT(Just in time,即时编译,边运行边编译)、AOT(Ahead Of Time,运行前编译),是两种程序的编译方式

    JIT(Just in time,即时编译,边运行边编译).AOT(Ahead Of Time,运行前编译),是两种程序的编译方式

  6. 即时编译(JIT)

    即时编译(JIT : just-in-time compilation): 指计算机领域里,即时编译也被成为动态翻译,是一种通过在运行时将字节码翻译为机器码,从而改善字节码编译语言性能的技术 即时编译 ...

  7. 转:什么是即时编译(JIT)!?OpenJDK HotSpot VM剖析

    重点 应用程序可以选择一个适当的即时编译器来进行接近机器级的性能优化. 分层编译由五层编译构成. 分层编译提供了极好的启动性能,并指导编译的下一层编译器提供高性能优化. 提供即时编译相关诊断信息的JV ...

  8. java编译过程(字节码编译和即时编译)

    Javac编译与JIT编译 简介: 编译包括两种情况: 1,源码编译成字节码 2,字节码编译成本地机器码(符合本地系统专属的指令) 解释执行也包括两种情况: 1,源码解释执行 2,字节码解释执行 解释 ...

  9. JVM JIT动态编译

    一.概述 1.1 基本概念 a. 动态编译(dynamic compilation)指的是"在运行时进行编译":与之相对的是事前编译(ahead-of-time compilati ...

随机推荐

  1. ubuntu 13.04 64位安装32位兼容包

    未安装32位兼容包,编译出现未找到指定目录下gcc命令,则安装兼容包:sudo apt-get install ia32-libs 若提示:不能安装,与相关软件冲突,则按如下安装: 在网上找到更新源, ...

  2. MyBatis 笔记总结

    1.MyBatis中的一些要点: 1.1 SqlMapConfig.xml:mybatis的全局配置文件,配置mybatis的运行环境等信息,包括mapper.xml文件 1.2 mapper.xml ...

  3. Oracle官方非托管Odac驱动与Oracle官方托管odac驱动

    方便自己,方便他人,记一次连接oracle的经历,使用 [Oracle官方非托管Odac驱动,Oracle.DataAccess.Client]连接数据库的时候程序会报错,找了很久都不知道是什么原因, ...

  4. Linux系列教程(十一)——Linux软件包管理之RPM命令

    前面我们介绍了Linux系统的常用命令介绍和文本编辑器vim命令的介绍.那么从这篇博客开始,我们会正式的讲解Linux的系统管理,首先要讲的是Linux的软件包管理. 1.Linux软件包分类 一.源 ...

  5. Java语言写出水仙花数,

    package com.llh.demo;/** * 水仙花数 * @author llh * */public class Demo14 {    public static void main(S ...

  6. Linux学习(十四)磁盘格式化、磁盘挂载、手动增加swap空间

    一.磁盘格式化 分好去的磁盘需要格式化之后才可以使用.磁盘分区一般用mke2fs命令或者mkfs.filesystemtype.这个filesystemtype分为ext4,ext3,xfs等等.xf ...

  7. linux学习(十一)用户和用户组管理

    一.用户文件 文件:/etc/passwd 这个文件记录了用户了用户名,用户id,所属组,家目录,shell信息: [root@iZ25lzba47vZ ~]# tail -n3 /etc/passw ...

  8. 如何阻止sql注入(pdo篇)

    Use prepared statements and parameterized queries. These are SQL statements that are sent to and par ...

  9. Leetcode题解(32)

    107. Binary Tree Level Order Traversal II 题目 直接代码: /** * Definition for a binary tree node. * struct ...

  10. Problem B

    Problem Description A subsequence of a given sequence is the given sequence with some elements (poss ...