JVM---执行引擎
概述
/**
* 【执行引擎-概述】
* what?
* Execution Engine;
* 执行引擎 是JVM核心的组成部分之一;
*
* 虚拟机与物理机的 执行引擎:
* 同:
* 都有代码执行能力;
* 异:
* 物理机:
* 执行引擎 直接建立在 处理器、缓存、指令集、OS 上的;
* 虚拟机:
* 执行引擎 由软件自行实现,不受物理条件制约地 定制指令集及执行引擎的结构体系,能够执行不被硬件直接支持的指令集格式;
*
* JVM的主要任务是 负责装载字节码到JVM内部,但 字节码并不能直接运行在OS上;
* (字节码指令并非等价于本地机器指令,JVM内部的只是能被JVM识别的字节码指令、符号表、其他辅助信息...)
*
* 作用:
* 将 字节码指令 解释/编译 为 对应平台的本地机器指令;
*
* 工作过程:
* 执行引擎 在执行过程中 需要执行什么样的字节码指令 完全依赖于 PC寄存器;
* 每当执行完一条字节码指令,PC寄存器 就会更新下一条需要被执行的 字节码指令地址;
* 方法在执行过程中,执行引擎 可能会通过 存储在局部变量表中的对象引用 准确定位到 存储在堆区的对象实例信息;
* 通过 对象头中的 元数据指针 定位到 目标对象的类型信息;
*/
Java代码编译和执行过程
/**
* 【执行引擎-Java代码编译和执行的过程】
* 大部分的程序代码 转换成 物理机的目标代码(或虚拟机能执行的指令集)之前,都需要经过以下各个步骤:
*
* 程序源码 -> 词法分析 -> 单词流 -> 语法分析 -> 抽象语法树 -> 指令流(可选) -> 解释器 -> 解释执行
* 【javac实现】 【字节码解释器interpreter】
*
* -> 优选器(可选) -> 中间代码(可选) -> 生成器 -> 目标代码
* 【JIT编译器】
*
* 解释器 interpreter:
* 当JVM 启动时 会根据 预定义的规范 对字节码 采用逐行解释的方式执行;
* (将 每条字节码指令 翻译 为 对应平台的本地机器指令执行)
*
* JIT编译器 Just In Time Compiler:
* JVM 将 源代码 直接编译成 对应平台的本地机器指令;
*
* <机器指令->汇编语言->高级语言->字节码>
*
* 机器指令:
* what:
* CPU能直接识别并执行的指令;
* (二进制编码方式 表示)
*
* 组成:
* 操作码
* 指令的功能
* (该指令要完成的操作)
*
* 操作数
* 参与运算的对象
*
* 特点:
* 机器指令 与 CPU密切相关,不同种类的CPU对应的机器指令也不同;
*
*
* 指令:
* 由于 机器指令是0和1 组成的二进制序列,可读性太差,于是人们发明了指令;
* 指令 就是 把机器码的0和1序列,简化为 对应的指令(mov,inc...),可读性稍好;
* 不同的硬件平台,执行同一个操作,对应的机器指令可能不同;
*
*
* 指令集:
* 每个平台所支持的指令,称为 对应平台的指令集;
* eg:
* x86指令集,对应 x86架构的平台;
* arm指令集,对应 arm架构的平台;
*
* 汇编语言:
* 由于 机器指令 可读性太差,于是 发明了 汇编语言;
*
* 在汇编语言中,用 助记符代替机器指令的 操作码,
* 用地址符号或标号代替指令或操作数的地址;
*
* 特定的汇编语言和特定的机器语言指令集是一一对应的,不同平台之间不可直接移植;
*
* 工作方式:
* 在不同的硬件平台中,汇编语言对应着不同的机器语言指令集,通过汇编过程转换成机器指令;
*
*
*
* 高级语言:
* 一种 独立于机器,面向过程或对象的语言;
*
* 工作方式:
* 高级语言设计的程序必须经过 解释或编译 为 本地机器指令 以后才能被机器执行;
*
* eg:
* C、C++ 源程序执行过程经历2个阶段:
* 1、编译: 源代码 -> 汇编代码
* 读取 源程序,进行词法和语法分析,将高级语言指令转换为功能等效的汇编代码;
*
* 2、汇编: 汇编代码 -> 本地机器指令
* 把 汇编代码 翻译成 本地机器指令;
*
* 字节码:
* what?
* 中间码;
* 二进制代码;
* 比 机器指令更抽象;
*
* 实现方式:
* javac 将 Java源代码 编译为 字节码指令;
*
* 工作方式:
* 由 执行引擎 对字节码指令 进行 解释/编译 为对应平台的机器指令,交由CPU执行;
*/
执行方式
/**
* 【执行引擎-执行方式】
* 设置执行引擎执行方式:
* 当VM启动时,解释器首先发挥作用,不必等待JIT编译器全部编译完成再执行,这样可以省去很多不必要的编译时间;
*
* 并且随着程序运行时间的推移,JIT编译器逐渐发挥作用,根据热点探测功能,将有价值的字节码编译为本地机器指令,获得更高的执行效率;
*
* 设置执行引擎执行方式:
* 默认Hotspot VM采用 解释器与JIT编译器 并存的架构;
*
* 自定义执行方式:
* -Xint:
* 完全采用 解释器模式 执行程序;
*
* -Xcomp:
* 完全采用 JIT编译器模式 执行程序,如果JIT出现问题,解释器会介入执行;
*
* -Xmixed:
* 采用 解释器+JIT编译器 混合模式 执行程序;
*
*/
解释器
/**
* 【执行引擎-解释器 interpreter】
* 工作方式:
* 当JVM 启动时 会根据 预定义的规范 对字节码指令 采用逐行解释的方式执行;
* (将 每条字节码指令 翻译 为 对应平台的本地机器指令执行)
*
* 解释器分类:
* 字节码解释器:
* (Java源码 -> 字节码) -> (C++代码 -> 本地机器指令)
* javac 执行引擎-字节码解释器
*
* 模板解释器:
*
*/
即时编译器
/**
* 【执行引擎-即时编译器 Just In Time Compiler】
*
* what?
* 前端编译器
* .java -> .class
* eg:javac
*
* 后端编译器
* 字节码指令 -> 本地机器指令
* eg:Hotspot VM的C1、C2
*
* JIT分类:
* Hotspot VM中,有2个JIT编译器:
* Client Compiler(C1编译器)、Server Compiler(C2编译器)
*
* 自定义使用某个JIT编译器:
* -client
*
* 指定JVM在client模式下运行,并使用 C1编译器;
*
* C1编译器 对字节码 进行简单可靠的优化,耗时短;
*
* -server
*
* (64位默认使用 Server模式)
* 指定JVM在server模式下运行,并使用 C1编译器;
*
* C2编译器 耗时较长的优化,以及激进优化;
*
* C1编译器 和 C2编译器 不同优化策略:
* C1:
* 方法内联
* 将引用的函数代码编译到引用点处,这样可以减少栈桢的生成,减少参数传递以及跳转过程;
* 去虚拟化
* 对唯一的实现类进行内联;
* 冗余消除
* 在运行期间把一些不会执行的代码折叠掉
*
* C2:
* 优化主要是在全局层面,逃逸分析是优化的基础;
* 基于逃逸分析的几种优化:
* 标量替换:
* 用标量值 代替聚合对象的属性值
* 栈上分配:
* 对于未逃逸对象分配在栈
* 同步消除:
* 清除同步操作,通常指synchronized
*
* JIT编译器执行方式:
* 将 JIT编译请求 放入队列中(VM_THREAD),由一个异步线程监听处理队列内容
*
*/
分层编译
/**
* 【执行引擎-JIT编译器-分层编译】
* Java 7开始引入了分层编译的概念;
* 结合了C1和C2的优势,追求启动速度和峰值性能的一个平衡;
*/
热点代码及探测方式
/**
* 【执行引擎-JIT编译器-热点代码及探测方式】
* 是否需要启动JIT编译器将字节码直接编译为本地机器指令,需要根据代码被调用执行的频率而定;
*
* 热点代码:
* 需要被编译为本地机器指令的字节码;
*
* JIT编译器 在运行时 会对 频繁被调用的热点代码做出深度优化,将其直接编译为本地机器指令,以提升Java程序执行性能;
*
* ***哪些字节码是热点代码?
* 一个被调用多次的方法;
* 一个方法体内循环次数较多的循环体;
*
* ***一个方法究竟被调用多少次(或一个循环体执行多少次循环)才能达到热点标准?
* 依靠热点探测功能;
*
* 热点探测功能实现:
* Hotspot VM采用 基于计数器的热点探测;
* Hotspot VM为每个方法都建立2个不同类型的计数器:
* a,方法调用计数器:
* 统计方法调用次数
*
* 默认阈值 client模式 1500次,server模式 10000次
*
* 设置方法调用计数器阈值:
* -XX:CompileThreshold
*
* <热度衰减>
* 如果不做任何阈值设置,方法调用计数器统计的并不是方法被调用的次数,而是一个相对的执行频率(一段时间内方法被调用次数);
*
* 当超过一定的时间限度,如果方法的调用次数仍不足以让它提交给JIT编译器编译,那这个方法的调用计数器就被减少一半,这个过程称为方法调用计数器的热度衰减,这段时间称为方法统计的半衰周期;
*
* 热点衰减 是在虚拟机进行垃圾回收时顺便进行的;
*
* 关闭热点衰减:
* -XX:-UseCounterDecay
*
* 关闭后,只要系统运行时间足够长,绝大部分方法都被编译为本地机器指令;
*
* 设置半衰周期的时间:
* -XX:CounterHalfLifeTime ,单位秒
*
* b,回边计数器:
* 统计循环体执行的循环次数
*
* <热点代码缓存>
*
* java -XX:+PrintFlagsFinal -version | grep CodeCache
*
* 位置:
* 方法区
*
* 默认大小:
* server 2496KB起
* client 160KB起
*
* 最大大小:
*
*/
疑问
/**
* 【疑问】
* 1、为什么Java是半编译半解释型语言?
* a, Java源码 -> 字节码 (javac)
* b, 执行引擎 解释执行/编译执行 字节码
*
* 2、既然Hotspot VM中已经内置了JIT编译器了,为什么还需要性能低的解释器?
* 当程序启动后,解释器可以马上发挥作用,省去编译时间,立即执行;
*
* JIT编译器想发挥作用,需要把代码编译为本地机器指令,需要一定执行时间;
*/
方法调用计数器

浙公网安备 33010602011771号