0%
这是一片思考的空间 -- arthinking
Spring 重构&代码整洁之道 软件设计 JVM 并发编程 数据结构与算法 分布式 存储 网络 微服务 设计模式
Java技术栈 - 涉及Java技术体系

JVM笔记 - 程序编译与代码优化(晚期(运行期)优化)

《深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)》笔记

1、概述

即时编译器并不是虚拟机必需的部分。

本章提及的编译器、即时编译器都是指 HotSpot 虚拟机内的即时编译器,虚拟机也是特指 HotSpot 虚拟机。

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

2.1、解释器与编译器

HotSpot 虚拟机中内置了两个即时编译器,分别称为 Client Compiler 和 Server   Compiler。

HotSpot 虚拟机会根据自身版本与宿主机器的硬件性能自动选择运行模式,用户也可以使用"- client" 或"- server" 参数去强制指定虚拟机运行在 Client 模式或 Server 模式。

参数"- Xint" 强制虚拟机运行于“解释模式”( Interpreted   Mode)。

参数"- Xcomp" 强制虚拟机运行于“编译模式”( Compiled   Mode),这时将优先采用编译方式执行程序,但是解释器仍然要在编译无法进行的情况下介入执行过程。

为了在程序启动响应速度与运行效率之间达到最佳平衡, HotSpot 虚拟机还会逐渐启用分层编译( Tiered Compilation)[ 4] 的策略。

实施分层编译后, Client   Compiler 和 Server   Compiler 将会同时工作,许多代码都可能会被多次编译,用 Client   Compiler 获取更高的编译速度,用 Server   Compiler 来获取更好的编译质量,在解释执行的时候也无须再承担收集性能监控信息的任务。

2.2、编译对象与触发条件

“热点代码”有两类,即:被多次调用的方法。被多次执行的循环体。

这种编译方式因为编译发生在方法执行过程之中,因此形象地称之为栈上替换( On   Stack   Replacement, 简称为 OSR 编译,即方法栈帧还在栈上,方法就被替换了)。

判断一段代码是不是热点代码,是不是需要触发即时编译,这样的行为称为热点探测。

目前主要的热点探测判定方式有两种:基于采样的热点探测,基于计数器的热点探测。

在 HotSpot 虚拟机中使用的是第二种——基于计数器的热点探测方法,因此它为每个方法准备了两类计数器:方法调用计数器( Invocation   Counter) 和回边计数器( Back   Edge   Counter)。

当计数器超过阈值溢出了,就会触发 JIT 编译。

当编译工作完成之后,这个方法的调用入口地址就会被系统自动改写成新的。

使用虚拟机参数- XX:- UseCounterDecay 来关闭热度衰减,让方法计数器统计方法调用的绝对次数。

使用- XX: CounterHalfLifeTime 参数设置半衰周期的时间,单位是秒。

回边计数器,它的作用是统计一个方法中循环体代码执行的次数。

建立回边计数器统计的目的就是为了触发 OSR 编译。

参数- XX: OnStackReplacePercentage 来间接调整回边计数器的阈值。

2.3、编译过程

在默认设置下,无论是方法调用产生的即时编译请求,还是 OSR 编译请求,虚拟机在代码编译器还未完成之前,都仍然将按照解释方式继续执行,而编译动作则在后台的编译线程中进行。

用户可以通过参数- XX:- BackgroundCompilation 来禁止后台编译。

对于 Client   Compiler 来说,它是一个简单快速的三段式编译器,主要的关注点在于局部性的优化,而放弃了许多耗时较长的全局优化手段。

而 Server Compiler 则是专门面向服务端的典型应用并为服务端的性能配置特别调整过的编译器,也是一个充分优化过的高级编译器,几乎能达到 GNU C++编译器使用-O2参数时的优化强度。

2.4、查看及分析即时编译结果

3、编译优化技术

3.1、优化技术概览

这些代码优化变换是建立在代码的某种中间表示或机器码之上,绝不是建立在Java源码之上的。

3.2、公共子表达式消除

3.3、数组边界检查消除

除了如数组边界检查优化这种尽可能把运行期检查提到编译器完成的思路之外,另外还有一种避免思路:隐式异常处理。

当 foo 不为空的时候,对 value 的访问是不会额外消耗一次对 foo 判空的开销的。代价就是当 foo 真的为空时,必须转入到异常处理器中恢复并抛出 NullPointException异常,这个过程必须从用户态转动内核态中处理,结束后再回到用户态,速度远比一次判空检查慢。

3.4、方法内联

只有使用invokespecial指令调用的私有方法、实例构造器、父类方法以及使用invokestatic指令进行调用的静态方法才是在编译期进行解析的。

3.5、逃逸分析

逃逸分析的基本行为就是分析对象动态作用域。

如果确定一个方法不会逃逸出方法之外,那让整个对象在栈上分配内存将会是一个很不错的主意,对象所占用的内存空间就可以随栈帧而销毁。在一般应用中,不会逃逸的局部对象所占用的比例很大,如果能使用栈上分配,那大量的对象就会随着方法结束而自动销毁了,垃圾手机系统的压力将会小很多。

同步消除

标量替换

4、Java与C/C++的编译器对比

除了它们自身的API库实现得好坏之外,其余的比较就成了一场“拼编译器”和“拼输出代码质量”的游戏。

Java虚拟机的即时编译器与C/C++的静态优化编译器相比,可能会由于下列这些原因导致输出的本地代码有一些劣势:

即时编译器运行占用的是用户程序的运行时间

Java语言是动态的类型安全语言

Java语言中虽然没有virtual关键字,但是使用虚方法的频率却远远大于C/C++语言

Java语言是可以动态扩展的语言

Java语言中对象的内存分配都是在堆上进行的,只有方法中的局部变量才能在堆上分配

5、本章小结

欢迎关注我的其它发布渠道

订阅IT宅
内功修炼
Java技术栈
Java架构杂谈是IT宅精品文章公众号,欢迎订阅:
📄 网络基础知识:两万字长文50+张趣图带你领悟网络编程的内功心法 📄 HTTP发展史:三万长文50+趣图带你领悟web编程的内功心法 📄 HTTP/1.1:可扩展,可靠性,请求应答,无状态,明文传输 📄 HTTP/1.1报文详解:Method,URI,URL,消息头,消息体,状态行 📄 HTTP常用请求头大揭秘 📄 HTTPS:网络安全攻坚战 📄 HTTP/2:网络安全传输的快车道 📄 HTTP/3:让传输效率再一次起飞 📄 高性能网络编程:图解Socket核心内幕以及五大IO模型 📄 高性能网络编程:三分钟短文快速了解信号驱动式IO 📄 高性能网络编程:彻底弄懂IO复用 - IO处理杀手锏,带您深入了解select,poll,epoll 📄 高性能网络编程:异步IO:新时代的IO处理利器 📄 高性能网络编程:网络编程范式 - 高性能服务器就这么回事 📄 高性能网络编程:性能追击 - 万字长文30+图揭秘8大主流服务器程序线程模型
📄 Java内存模型:如果有人给你撕逼Java内存模型,就把这些问题甩给他 📄 一文带你彻底理解同步和锁的本质(干货) 📄 AQS与并发包中锁的通用实现 📄 ReentrantLock介绍与使用 📄 ReentrantReadWriteLock介绍与使用 📄 ReentrantLock的Condition原理解析 📄 如何优雅的中断线程 📄 如何优雅的挂起线程 📄 图解几个好玩的并发辅助工具类 📄 图解BlockingQueue阻塞队列
📄 消息队列那么多,为什么建议深入了解下RabbitMQ? 📄 高并发异步解耦利器:RocketMQ究竟强在哪里? 📄 Kafka必知必会18问:30+图带您看透Kafka
📄 洞悉MySQL底层架构:游走在缓冲与磁盘之间 📄 SQL运行内幕:从执行原理看调优的本质 📄 洞悉Redis技术内幕:缓存,数据结构,并发,集群与算法