深入理解JVM 第十一章 运行期优化

1 概述

在部分的商用虚拟机中,Java程序最初是通过解释器(Interpreter)进行解释执行的,当虚拟机发现某个方法或代码块的运行特别频繁,就会把这些代码认定为"热点代码(Hot Spot Code)",为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化,完成这个任务的编译器称为即时编译器(Just In Time Complier,JIT)。JIT编译性能的好坏、代码优化程度的高低时衡量一款商用虚拟机优秀与否的最关键的指标之一,它也时虚拟机中最核心最能体现技术水平的部分。

2 HotSpot虚拟机内的即时编译器

2.1 解释器与编译器

当程序需要迅速启动和执行的时候,解释器可以首先发挥作用,省区编译的时间,立即执行。当程序运行后,随着时间的推移,编译器逐渐发挥作用,把越来越多的代码编译成本地代码之后,可以获得更高的执行效率。当程序运行环境中内存资源限制较大(如部分嵌入式系统中),可以使用解释执行节约内存,反之可以使用编译执行来提升效率。同时,解释器还可以作为编译器激进优化时的一个"逃生门",让编译器根据概率选择一些大多数时候都能提升运行速度的优化手段,当激进优化的假设不成立,如加载了新类后类型继承结构出现变化、出现"罕见陷阱(Uncommon Trap)“时可以通过逆优化(Deoptimization)退回到解释状态继续执行。因此在这个虚拟机执行架构中,解释器与编译器经常时相辅相成地配合工作。
在这里插入图片描述
HotSpot虚拟机中内置了两个即时编译器,称为Client Complier和Server Complier,或者简称C1编译器和C2编译器。用户可以使用参数来选择使用"解释模式”、“编译模式"还是"混合模式”。

分层编译策略:
0层:解释执行(Interpreter)
1层:使用C1即时编译器编译执行(不带profiling)
2层:使用C1即时编译器编译执行(带基本的profiling)
3层:使用C1即时编译器编译执行(带完全的profiling)
4层:使用C2即时编译器编译执行

profiling是指在运行过程中收集一些程序执行状态数据,例如【方法的调用次数】,【循环的回边次数】等

即时编译器(JIT)与解释器的区别
a. 解释器是将字节码解释为机器码,下次即使遇到相同的字节码,仍会执行重复的解释。
b. JIT是将一些字节码编译为机器码,并存入Code Cache,下次遇到相同的代码,直接执行,无需再编译。
c. 解释器是将字节码解释为针对所有平台都通用的机器码。
d. JIT会根据平台类型,生成平台特定的机器码。

3 编译优化技术

3.1 公共子表达式消除

公共子表达式消除是一个普遍应用于各种编译器的经典优化技术,它的含义是:如果一个表达式E已经被计算过了,并且从先前的计算到现在E中所有变量的值都没有发生变化,那么E的这次出现就称为了公共子表达式。对这种表达式,没有必要花时间再对它进行计算,只需要直接用前面计算过的表达式结果代替E就可以了。如果这种优化仅限于程序的基本块内,便称为局部公共子表达式消除(Local Common Subexpression Elimination),如果这种优化的范围涵盖了多个基本块,那就 称为全局公共子表达式消除(Globall Common Subexpression Elimination)。

3.2 数组边界检查消除

数组边界检查 消除(Array Bounds Checking Elimination)是即时编译器中的一项语言相关的经典优化技术。为了安全,数组边界检查肯定是必须做的,但数组边界检查是不是必须在运行期间一次不漏地检查则是可以"商量"的事。例如,数组下标事一个常量,如foo[3],只要在编译期根据数据流分析来确定foo.length的值,并判断下标"3"没有越界,执行的时候就无须判断了。更加常见的情况是数组访问发生在循环之中,并且使用循环变量来进行数组的访问,如果编译器只要通过数据流分析就可以判定循环变量的取值范围永远在去缉拿[0,foo.length]之内,那再整个循环中就可以把数组的上下界检查消除,这可以节省很多次的条件判断操作。

3.3 逃逸分析

逃逸分析的基本行为就是分析对象动态作用域:当一个对象在方法里面被定义后,它可能被外部方法所引用,例如作为调用参数传递倒其他方法中,这种行为称为方法逃逸。甚至还有可能被外部线程访问到,譬如赋值给类变量或可以在其他线程中访问的实例变量,这种行为称为线程逃逸。

如果能够证明一个对象不会逃逸到方法或线程之外,也就是别的方法或线程无法通过任何途径访问到这个对象,则可能为这个变量进行一些高效的优化,如:
**栈上分配(Stack Allocations)😗*如果确定一个对象不会逃逸出方法之外,那让这个对象在栈上分配内存将会是一个很不错的注意,对象所占用的内存空间就可以随栈帧出栈而销毁。在一般应用中,不会逃逸的局部对象所占的比例很大,如果能使用栈上分配,那大量的对象就会随着方法的结束而自动销毁了,垃圾收集系统的压力将会小很多。
**同步消除(Synchronization Elimination):**线程同步本身就是一个相对耗时的过程,如果逃逸分析能够确定一个变量不会逃逸出线程,无法被其他线程访问,那这个变量的读写肯定就不会有竞争,对这个变量实施的同步措施也就可以消除掉。
**标量替换(Scalar Replacement):**标量(Scalar)是指一个数据已经无法再分解成更小的数据来表示了 ,Java虚拟机中的原始数据类型(int、long等数值类型及reference类型等)都不能再进一步分解,它们就i可以被称为标量。相对的,如果一个数据可以继续费解, 那它就被称作聚合量(Aggregate),Java中的对象就是最典型的 聚合量。如果把一个Java对象拆散,根据程序访问情况,将其使用到的成员变量恢复原始类型来访问就叫做标量替换。如果逃逸分析证明一个对象不会被外部访问,并且这个对象可以被拆散的话,那程序真正执行的时候将可能不创建这个对象,而改为直接创建它的若干个被这个方法使用到的成员变量来代替。将对象拆分后,除了可以让对象的成员变量在栈上(栈上存储的数据,很大机会会被虚拟机分配至物理机器的高速寄存器中存储)分配和读写之外,还可以为后续进一步的优化手段创建条件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值