即时编译器在JVM调优战场的决胜策略

2024-03-22 19:20

本文主要是介绍即时编译器在JVM调优战场的决胜策略,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

        

目录

一、方法内联

二、循环展开

三、分支预测

四、逃逸分析

        4.1 栈上分配

        4.2 标量替换

        4.3 同步消除

五、冗余消除


        JVM中的即时编译器(如HotSpot的C1、C2编译器)会对代码进行即时编译优化,即时编译优化(Just-In-Time Compilation Optimization)是Java虚拟机(JVM)为了提升运行时性能而采取的一种策略。

        即时编译优化主要包括以下几个方面,下面来详细介绍一下

一、方法内联

        目前主流的商用Java虚拟机里,Java程序都是通过解释器进行解释执行的,当虚拟机发现某个方法或代码块的运行特别频繁,就会把这些代码认定为“热点代码”,为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成本地机器码,并以各种手段尽可能地进行优化,被称为方法内联。

        通过这种方式来减少栈帧的创建和销毁以及参数传递等操作,从而减少方法调用的开销,提高性能。

        然而,方法内联并非总是有益的,如果内联后的代码量过大可能会导致内存占用增加,并且会降低JIT编译速度。因此,JIT编译器通常会根据一些策略判断是否以及如何进行内联,例如考虑方法大小、调用频率、方法体复杂度等因素。

二、循环展开

        循环展开是一种代码优化技术,它发生在Java虚拟机(JVM)在运行时将热点代码(即频繁执行的字节码)转化为机器码的过程中。循环展开的具体做法是将循环体内的部分或全部迭代复制多次,从而减少循环控制结构的开销。

        例如,原始的循环代码可能是这样的:

for (int i = 0; i < loopCount; i++) {// 执行某些操作...
}

        经过循环展开后,可能变成如下形式:

int unrolledLoopCount = loopCount / UNROLL_FACTOR;
for (int i = 0; i < unrolledLoopCount; i++) {// 执行某些操作...// 执行相同的操作...// ...重复UNROLL_FACTOR次
}// 如果loopCount不能整除UNROLL_FACTOR,则处理剩余迭代次数
if (loopCount % UNROLL_FACTOR != 0) {for (int i = unrolledLoopCount * UNROLL_FACTOR; i < loopCount; i++) {// 执行剩余的迭代次数}
}

        循环展开带来的潜在优势:

  1. 降低循环控制的开销:减少了判断循环条件和更新循环变量的操作。
  2. 提高指令级并行性:对于支持多核处理器和超标量架构的系统来说,展开后的循环可以提供更多的并发执行机会。
  3. 增强缓存局部性:当循环体内的操作涉及到内存访问时,循环展开有可能提高数据在CPU缓存中的命中率,从而加速执行速度。

        过度的循环展开可能导致代码膨胀、寄存器压力增大以及缓存效率下降等问题,因此编译器通常会根据循环的特征、目标平台的特点以及性能分析结果来智能地选择是否以及如何展开循环。

三、分支预测

        即时编译的分支预测优化是 Java 虚拟机(JVM)在运行时对热点代码进行性能优化的一种技术,特别是在即时编译器(Just-In-Time Compiler, JIT)阶段。分支预测是一种处理器级别的策略,用于猜测程序中的条件分支指令可能执行的路径,并预先加载相应的指令序列以减少实际跳转带来的延迟。

        在即时编译中,编译器不仅会考虑 CPU 硬件的分支预测功能,还会根据已有的运行信息和特定算法来进行更精确的分支预测优化。

四、逃逸分析

        逃逸分析的基本原理是:分析对象动态作用域,当一个对象在方法里被定义后,他可能被外部方法引用,例如作为调用参数传递到其他方法,这种称为方法逃逸;甚至还有可能被外部线程访问到,譬如赋值给可以在其他线程中访问的实例变量,这种称为线程逃逸;从不逃逸、方法逃逸到线程逃逸,称为对象由低到高的不同逃逸程度。

        如果能证明一个对象不会逃逸到方法或线程外,或者逃逸程度比较低(只逃逸出方法而不会逃逸出线程),则可能为这个对象实例采取不同程度的优化。

        4.1 栈上分配

        在 Java 虚拟机中,Java 堆上分配创建对象的内存空间几乎是常识,Java 堆中的对象对于各个线程都是共享和可见的,只要持有这个对象的引用,就可以访问到堆中存储的对象数据。

        虚拟机的垃圾收集子系统会回收堆中不在使用的对象,但回收动作无论是标记筛选出可回收对象,还是回收和整理内存,都需要耗费大量资源。

        如果确定一个对象不会逃逸出线程之外,那让对象在栈上分配将会是一个不错的选择,对象所占用的内存空间就可以随栈帧出栈而销毁。

        在一般应用中完全不会逃逸的局部对象和不会逃逸出线程的对象所占的比例是很大的,如果能使用栈上分配,那大量的对象就会随着方法的结束而自动销毁,垃圾收集子系统的压力会下降很多。栈上逃逸可以支持方法逃逸,但不支持线程逃逸。

        4.2 标量替换

        若一个数据已经无法再分解成更小的数据来表示了,Java虚拟机中的原始数据(int、long等数值类型及reference类型等)都不能在进一步分解了,那么这些数据就可以被称为标量。

        相对的,如果一个数据可以继续分解,被称为聚合量,Java对象就是典型的聚合量。如果把一个Java对象拆散,根据程序访问情况,将其用到的成员变量恢复为原始类型来访问,这个过程称为标量替换。

        假如逃逸分析能证明一个对象不会被方法外部访问,并且这个对象可以拆散,那么程序真正执行的时候将可能不去创建这个对象,而改为直接创建它的若干被这个方法使用的成员变量来替代。

        将对象拆分后,除了可以让对象的成员变量在栈上分配和读写外,还可以为后续进一步优化创建条件。标量替换可以视作栈上分配的一种特例,实现更简单,但对逃逸程度的要求更高,它不允许对象逃逸出方法范围内。

        Jdk6 才开始初步支持逃逸分析。一直到 Jdk7 时这项优化才成为服务端编译器默认开启的选项。如果需要,或者确认对程序运行有益,用户可以使用参数 --XX:+DoEscapeAnalysis 来手动开启逃逸分析,开启之后可以通过参数 -XX:PrintEscapeAnalysis 来查看分析结果。有了逃逸分析之后,可以使用参数 -XX:EliminateAllocations 来开启标量替换,使用 +XX;EliminateLocks 来开启同步消除。

        4.3 同步消除

        线程同步本身是一个相对耗时的过程,如果逃逸分析能够确定一个变量不会逃逸出线程,无法被其他线程访问,那么这个变量的读写肯定就不会有竞争,对于这个变量实施的同步措施也就可以安全地消除掉。

五、冗余消除

        冗余消除是一种优化技术,用于减少生成的机器码中的不必要的重复计算和指令。在运行时,JIT 编译器会对热点代码(即频繁执行的代码段)进行分析,并尝试找出那些可以提前计算、存储结果并在后续执行中复用的情况,或者发现那些总是产生相同结果的条件检查和分支等。

        冗余消除的具体形式包括:

  • 循环不变量外提:将循环内部不会变化的计算移动到循环外部,避免每次迭代都进行相同的计算。
  • 常量折叠与代数简化:识别出已知的常量表达式并预先计算它们的结果,或对代数表达式进行简化。
  • 死代码删除:移除那些无论如何都不会被执行到的代码。
  • 公共子表达式消除:如果一个表达式的值已经被计算过,那么之后再次遇到该表达式时就可以直接使用之前的结果,而不是重新计算。
  • 数组边界检查消除:对于确定安全的数组访问,编译器可以消除每次数组元素访问时的边界检查。

        通过这些冗余消除技术,即时编译器能够提高程序的运行效率,减少CPU周期和内存访问次数,进而提升应用程序的整体性能。

        总结,即时编译优化对于 Java 应用的性能至关重要,尤其是针对长时间运行且有大量热点代码的应用场景,通过合理配置 JVM 参数及深入了解即时编译机制,可以在一定程度上有效提升系统整体性能。

往期经典推荐

JVM垃圾收集器你会选择吗?-CSDN博客

JVM内存模型深度解读-CSDN博客

一文看懂Nacos如何实现高效、动态的配置中心管理_nacos配置中心-CSDN博客

TiDB内核解密:揭秘其底层KV存储引擎如何玩转键值对-CSDN博客

领航分布式消息系统:一起探索Apache Kafka的核心术语及其应用场景-CSDN博客

这篇关于即时编译器在JVM调优战场的决胜策略的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/836034

相关文章

Java五子棋之坐标校正

上篇针对了Java项目中的解构思维,在这篇内容中我们不妨从整体项目中拆解拿出一个非常重要的五子棋逻辑实现:坐标校正,我们如何使漫无目的鼠标点击变得有序化和可控化呢? 目录 一、从鼠标监听到获取坐标 1.MouseListener和MouseAdapter 2.mousePressed方法 二、坐标校正的具体实现方法 1.关于fillOval方法 2.坐标获取 3.坐标转换 4.坐

Spring Cloud:构建分布式系统的利器

引言 在当今的云计算和微服务架构时代,构建高效、可靠的分布式系统成为软件开发的重要任务。Spring Cloud 提供了一套完整的解决方案,帮助开发者快速构建分布式系统中的一些常见模式(例如配置管理、服务发现、断路器等)。本文将探讨 Spring Cloud 的定义、核心组件、应用场景以及未来的发展趋势。 什么是 Spring Cloud Spring Cloud 是一个基于 Spring

Javascript高级程序设计(第四版)--学习记录之变量、内存

原始值与引用值 原始值:简单的数据即基础数据类型,按值访问。 引用值:由多个值构成的对象即复杂数据类型,按引用访问。 动态属性 对于引用值而言,可以随时添加、修改和删除其属性和方法。 let person = new Object();person.name = 'Jason';person.age = 42;console.log(person.name,person.age);//'J

java8的新特性之一(Java Lambda表达式)

1:Java8的新特性 Lambda 表达式: 允许以更简洁的方式表示匿名函数(或称为闭包)。可以将Lambda表达式作为参数传递给方法或赋值给函数式接口类型的变量。 Stream API: 提供了一种处理集合数据的流式处理方式,支持函数式编程风格。 允许以声明性方式处理数据集合(如List、Set等)。提供了一系列操作,如map、filter、reduce等,以支持复杂的查询和转

Java面试八股之怎么通过Java程序判断JVM是32位还是64位

怎么通过Java程序判断JVM是32位还是64位 可以通过Java程序内部检查系统属性来判断当前运行的JVM是32位还是64位。以下是一个简单的方法: public class JvmBitCheck {public static void main(String[] args) {String arch = System.getProperty("os.arch");String dataM

详细分析Springmvc中的@ModelAttribute基本知识(附Demo)

目录 前言1. 注解用法1.1 方法参数1.2 方法1.3 类 2. 注解场景2.1 表单参数2.2 AJAX请求2.3 文件上传 3. 实战4. 总结 前言 将请求参数绑定到模型对象上,或者在请求处理之前添加模型属性 可以在方法参数、方法或者类上使用 一般适用这几种场景: 表单处理:通过 @ModelAttribute 将表单数据绑定到模型对象上预处理逻辑:在请求处理之前

eclipse运行springboot项目,找不到主类

解决办法尝试了很多种,下载sts压缩包行不通。最后解决办法如图: help--->Eclipse Marketplace--->Popular--->找到Spring Tools 3---->Installed。

JAVA读取MongoDB中的二进制图片并显示在页面上

1:Jsp页面: <td><img src="${ctx}/mongoImg/show"></td> 2:xml配置: <?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001

Java面试题:通过实例说明内连接、左外连接和右外连接的区别

在 SQL 中,连接(JOIN)用于在多个表之间组合行。最常用的连接类型是内连接(INNER JOIN)、左外连接(LEFT OUTER JOIN)和右外连接(RIGHT OUTER JOIN)。它们的主要区别在于它们如何处理表之间的匹配和不匹配行。下面是每种连接的详细说明和示例。 表示例 假设有两个表:Customers 和 Orders。 Customers CustomerIDCus

22.手绘Spring DI运行时序图

1.依赖注入发生的时间 当Spring loC容器完成了 Bean定义资源的定位、载入和解析注册以后,loC容器中已经管理类Bean 定义的相关数据,但是此时loC容器还没有对所管理的Bean进行依赖注入,依赖注入在以下两种情况 发生: 、用户第一次调用getBean()方法时,loC容器触发依赖注入。 、当用户在配置文件中将<bean>元素配置了 lazy-init二false属性,即让