深入理解java虚拟机笔记Chapter11
运行期优化

即时编译
什么是即时编译?
- 当虚拟机发现某个方法或某段代码运行的特别频繁时,会把这段代码认为成热点代码;
- 在运行时,虚拟机会将这段代码编译成平台相关的机器码,并进行各种层次的优化。
HotSpot 虚拟机内的即时编译器运作过程
我们主要通过以下 5 个问题来了解 HotSpot 虚拟机的即时编译器。
为什么要使用解释器与编译器并存的架构?
- 解释器的优点:可以提高程序的响应速度(省去了编译的时间),并且节约内存。
- 编译器的优点:可以提高执行效率。
- 虚拟机参数设置:
- 强制运行于解析模式:
-Xint,编译器完全不工作; - 强制运行于编译模式:
-Xcomp,当编译器编译失败时,解释执行还是会介入的。
- 强制运行于解析模式:
为什么虚拟机要实现两个不同的 JIT 编译器?
- Client Compiler(C1):不激进优化;
- Server Compiler(C2):激进优化,如果激进优化不成立,再退回为解释执行或者 C1 编译器执行。
什么是虚拟机的分层编译?
分层编译就是根据编译器编译、优化的规模与耗时,划分出不同的编译层次,在代码运行的过程中,可以动态的选择将某一部分代码片段提升一个编译层次或者降低一个编译层次。
C1 与 C2 编译器会同时工作,许多代码可能会被多次编译。
目的: 在程序的启动响应时间和运行效率间达到平衡。
编译层次的划分:
- 第 0 层:解释执行,不开启性能监控;
- 第 1 层:将字节编译为机器码,但不进行激进优化,有必要时会加入性能监控;
- 第 2 层及以上:将字节编译为机器码,会根据性能监控信息进行激进优化。
如何判断热点代码,触发编译?
1、什么是热点代码?
- 被多次调用的方法;
- 被多次执行的循环体;
- 虽然被判断为热点代码的是循环体,不过因为虚拟机的即时编译是以方法为单位的,所以编译器还是会将循环体所在的方法整个作为编译对象。
我们发现,判断热点代码的一个要点就是: 多次执行 。那么虚拟机是如何知道一个方法或者一个循环体被多次执行的呢?
2、什么是 “多次” 执行?
- 基于采样的热点探测
- 虚拟机周期检查各个线程的栈顶,如果发现一个方法经常出现在栈顶,则该方法为热点方法。
- 优点: 容易获取方法的调用关系,且简单高效。
- 缺点: 无法精准的判断一个方法的热度,并且容易受到线程阻塞的影响,如果一个方法由于它所在的线程被阻塞的缘故而一直出现在栈顶,我们并不能认为这个方法被调用的十分频繁。
- 基于计数器的热点探测
- 虚拟机为每一个方法(或代码块)建立一个计数器,一旦执行次数超过一定阈值,就将其判为热点代码。
- 优点: 精确严谨。
- 缺点: 不能直接获取方法的调用关系,且实现复杂。
- HotSpot 使用的是这个,并且还为每个方法建立了两个计数器。
2.1、HotSpot 中每个方法的 2 个计数器
- 方法调用计数器
- 统计方法被调用的次数,处理多次调用的方法的。
- 默认统计的不是方法调用的绝对次数,而是方法在一段时间内被调用的次数,如果超过这个时间限制还没有达到判为热点代码的阈值,则该方法的调用计数器值减半。
- 关闭热度衰减:-XX: -UseCounterDecay(此时方法计数器统计的是方法被调用的绝对次数);
- 设置半衰期时间:-XX: CounterHalfLifeTime(单位是秒);
- 热度衰减过程是在 GC 时顺便进行。
- 回边计数器
- 统计一个方法中 “回边” 的次数,处理多次执行的循环体的。
- 回边:在字节码中遇到控制流向后跳转的指令(不是所有循环体都是回边,空循环体是自己跳向自己,没有向后跳,不算回边)。
- 调整回边计数器阈值:-XX: OnStackReplacePercentage(OSR比率)
- Client 模式:回边计数器的阈值 = 方法调用计数器阈值 * OSR比率 / 100;
- Server 模式:回边计数器的阈值 = 方法调用计数器阈值 * ( OSR比率 - 解释器监控比率 ) / 100;
- 统计一个方法中 “回边” 的次数,处理多次执行的循环体的。
2.2、HotSpot 热点代码探测流程

热点代码编译的过程?
虚拟机在代码编译未完成时会按照解释方式继续执行,编译动作在后台的编译线程执行。
禁止后台编译:-XX: -BackgroundCompilation,打开后这个开关参数后,交编译请求的线程会等待编译完成,然后执行编译器输出的本地代码。
经典优化技术介绍
公共子表达式消除【语言无关】
如果一个表达式 E 已经计算过了,并且从先前的计算到现在,E 中所有变量值都没有发生变化,则 E 为公共子表达式,无需再次计算,直接用之前的结果替换。
举例
有表达式int d = (c * b) * 12 + a + (a + b * c),这段代码交给Javac编译器不会进行任何优化。
当这段代码进入到虚拟机即时编译器后,编译器检测到c * b和b * c是一样的表达式,因此这条公式变为int d = E * 12 + a + (a + E)。
还可能变为int d = E * 13 + a * 2。
数组范围检查消除【语言相关】
在循环中使用循环变量访问数组,如果可以判断循环变量的范围在数组的索引范围内,则可以消除整个循环的数组范围检查
方法内联【最重要】
目的是:去除方法调用的成本(如建立栈帧等),并为其他优化建立良好的基础,所以一般将方法内联放在优化序列最前端,因为它对其他优化有帮助。
类型继承关系分析(Class Hierarchy Analysis,CHA):用于确定在目前已加载的类中,某个接口是否有多于一种的实现,某个类是否存在子类、子类是否为抽象类等。
- 对于非虚方法:
- 直接进行内联,其调用方法的版本在编译时已经确定,是根据变量的静态类型决定的。
- 对于虚方法: (激进优化,要预留“逃生门”)
- 向 CHA 查询此方法在当前程序下是否有多个目标可选择;
- 只有一个目标版本:
- 先对这唯一的目标进行内联;
- 如果之后的执行中,虚拟机没有加载到会令这个方法接收者的继承关系发生改变的新类,则该内联代码可以一直使用;
- 如果加载到导致继承关系发生变化的新类,就抛弃已编译的代码。
- 有多个目标版本:
- 使用内联缓存,未发生方法调用前,内联缓存为空;
- 第一次调用发生后,记录调用方法的对象的版本信息;
- 之后的每次调用都要先与内联缓存中的对象版本信息进行比较;
- 版本信息一样,继续使用内联代码;
- 版本信息不一样,说明程序使用了虚方法的多态特性,这时取消内联,查找虚方法进行方法分派。
- 只有一个目标版本:
- 向 CHA 查询此方法在当前程序下是否有多个目标可选择;
逃逸分析【最前沿】
基本行为
分析对象的作用域,看它有没有能在当前作用域之外使用:
- 方法逃逸:对象在方法中定义之后,能被外部方法引用,如作为参数传递到了其他方法中。
- 线程逃逸:赋值给 static 变量,或可以在其他线程中访问的实例变量。
对于不会逃逸到方法或线程外的对象能进行优化
- 栈上分配: 对于不会逃逸到方法外的对象,可以在栈上分配内存,这样这个对象所占用的空间可以随栈帧出栈而销毁,减小 GC 的压力。
- 标量替换(重要):
- 标量:基本数据类型和 reference。
- 不创建对象,而是将对象拆分成一个一个标量,然后直接在栈上分配,是栈上分配的一种实现方式。
- HotSpot 使用的是标量替换而不是栈上分配,因为实现栈上分配需要更改大量假设了 “对象只能在堆中分配” 的代码。
- 锁消除: 不会逃逸到线程外的方法不需要进行同步。
虚拟机参数
- 开启逃逸分析:
-XX: +DoEscapeAnalysis - 开启标量替换:
-XX: +EliminateAnalysis - 开启锁消除:
-XX: +EliminateLocks - 查看分析结果:
-XX: PrintEscapeAnalysis - 查看标量替换情况:
-XX: PrintEliminateAllocations
优化案例
原始代码:
static class B {
int value;
final int get() {
return value;
}
}
public void foo() {
y = b.get();
// ...do stuff...
z = b.get();
sum = y + z;
}
第一步优化: 方法内联(一般放在优化序列最前端,因为对其他优化有帮助)
目的:
- 去除方法调用的成本(如建立栈帧等)
为其他优化建立良好的基础
public void foo() {
y = b.value;
// ...do stuff...
z = b.value;
sum = y + z;
}
第二步优化: 公共子表达式消除
public void foo() {
y = b.value;
// ...do stuff... // 因为这部分并没有改变 b.value 的值
// 如果把 b.value 看成一个表达式,就是公共表达式消除
z = y; // 把这一步的 b.value 替换成 y
sum = y + z;
}
第三步优化: 复写传播
public void foo() {
y = b.value;
// ...do stuff...
y = y; // z 变量与以相同,完全没有必要使用一个新的额外变量
// 所以将 z 替换为 y
sum = y + z;
}
第四步优化: 无用代码消除
无用代码:
- 永远不会执行的代码
- 完全没有意义的代码
public void foo() {
y = b.value;
// ...do stuff...
// y = y; 这句没有意义的,去除
sum = y + y;
}
深入理解java虚拟机笔记Chapter11的更多相关文章
- Java内存区域与内存溢出异常——深入理解Java虚拟机 笔记一
Java内存区域 对比与C和C++,Java程序员不需要时时刻刻在意对象的创建和删除过程造成的内存溢出.内存泄露等问题,Java虚拟机很好地帮助我们解决了内存管理的问题,但深入理解Java内存区域,有 ...
- 深入理解java虚拟机笔记Chapter12
(本节笔记的线程收录在线程/并发相关的笔记中,未在此处提及) Java内存模型 Java 内存模型主要由以下三部分构成:1 个主内存.n 个线程.n 个工作内存(与线程一一对应) 主内存与工作内存 J ...
- 深入理解Java虚拟机笔记
1. Java虚拟机所管理的内存 2. 对象创建过程 3. GC收集 4. HotSpot算法的实现 5. 垃圾收集器 6. 对象分配内存与回收细节 7. 类文件结构 8. 虚拟机类加载机制 9.类加 ...
- 深入理解java虚拟机笔记Chapter7
虚拟机类的加载机制 概述 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类的加载机制. 类加载的时机 J ...
- 深入理解java虚拟机笔记之一
Java的技术体系主要有支撑java程序运行的虚拟机,提供各开发领域接口支持Java API,java编程语言及许多第三方java框架( 如Spring,Structs等)构成. 可以把Java程序设 ...
- 深入理解Java虚拟机笔记——虚拟机类加载机制
目录 概述 动态加载和动态连接 类加载的时机 类的生命周期 被动引用 例子一(调用子类继承父类的字段) 例子二(数组) 例子三(静态常量) 类加载的过程 加载 验证 准备 解析 符号引用 直接引用 初 ...
- 【转载】深入理解Java虚拟机笔记---运行时栈帧结构
栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区的虚拟机栈(Virtual Machine Stack)的栈元素.栈帧存储了方法的局部变量表,操作 ...
- 深入理解java虚拟机笔记Chapter8
运行时栈帧结构 栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈(Virtual Machine Stack)的栈元素.栈帧存储了方法 ...
- 深入理解java虚拟机笔记Chapter2
java虚拟机运行时数据区 首先获取一个直观的认识: 程序计数器 线程私有.各条线程之间计数器互不影响,独立存储. 当前线程所执行的字节码行号指示器.字节码解释器工作时通过改变这个计数器值选取下一条需 ...
随机推荐
- 无法编译出.sys文件 寒江孤钓<<windows 内核安全编程>> 学习笔记
系统环境:win7 编译环境:Windows Win7 IA-64 Checked Build Environment 按照书中所说的步骤,出现如下问题 后来直接使用光盘源码,编译成功,于是对照源文件 ...
- Java 中 RMI 的使用
RMI 介绍 RMI (Remote Method Invocation) 模型是一种分布式对象应用,使用 RMI 技术可以使一个 JVM 中的对象,调用另一个 JVM 中的对象方法并获取调用结果.这 ...
- .NET生成小程序码,并合自定义背景图生成推广小程序二维码
前言: 对于小程序大家可能都非常熟悉了,随着小程序的不断普及越来越多的公司都开始推广使用起来了.今天接到一个需求就是生成小程序码,并且于运营给的推广图片合并在一起做成一张漂亮美观的推广二维码,扫码这种 ...
- Java 反编译工具哪家强?对比分析瞧一瞧
前言 Java 反编译,一听可能觉得高深莫测,其实反编译并不是什么特别高级的操作,Java 对于 Class 字节码文件的生成有着严格的要求,如果你非常熟悉 Java 虚拟机规范,了解 Class 字 ...
- 在ZOHO企业网盘中如何快速搜索文件?
现在越来越多的企业采用企业网盘来存储文档和资料,而且现在市面上的企业网盘各种各样.在使用企业网盘过程中,很多用户会问到企业网盘中如何快速搜索文件的问题.但是无论是"标签"功能还是普 ...
- [bug] jupyter notebook:服务在阿里云上启动,本地浏览器无法访问
问题 在阿里云上装了个jupyter,服务正常启动了,但网页上无法访问 排查 安全组已经设置过了,7777端口 在宝塔面板查看,发现7777端口并没有开,打开就可以访问了 原来阿里云的安全组和防火墙是 ...
- 进入除错模式!进入此模式后,将会出现更多的选项,分别是: · 以基本图形介面安装 CentOS 7 (使用标准显卡来设定安装流程图示); · 救援Centos系统; · 执行内存测试(Run a memory test);
Centos 7.3 安装 0.0392017.07.14 20:12:09字数 1550阅读 985 Centos 7.3 基于 Red Hat 企业版的源代码的最新版本的 CentOS 7 在今年 ...
- Linux_用户和组管理
一.用户分类 1.管理员和普通用户 1️⃣:管理员 -- 用户ID:0 2️⃣:普通用户 -- 用户ID:1-65535 2.普通用户又分为系统用户和登陆用户 1️⃣:系统用户 -- 用 ...
- Centos6.8安装mysql 步骤
第1步.查看CentOS下是否已安装mysql 输入命令 :yum list installed | grep mysql 第2步.删除已安装mysql 输入命令:yum -y remove mysq ...
- SPI接口在LCD上的应用
小分辨率的LCD,比如QQVGA,QCIF,QVGA等,广泛应用于功能手机和穿戴设备(比如手表)上.这类小分辨率的LCD,除了支持并行接口(比如i80),一般也会支持串行接口.在实际产品中广泛运用的 ...