性能优化:JIT即时编译与AOT提前编译

优质博文:IT-BLOG-CN

一、简介

JITAOT的区别: 两种不同的编译方式,主要区别在于是否处于运行时进行编译。

JIT:Just-in-time动态(即时)编译,边运行边编译:在程序运行时,根据算法计算出热点代码,然后进行JIT实时编译,这种方式吞吐量高,有运行时性能加成,可以跑得更快,并可以做到动态生成代码等,但是相对启动速度较慢,并需要一定时间和调用频率才能触发JIT的分层机制。JIT缺点就是编译需要占用运行时资源,会导致进程卡顿。

AOT:Ahead Of Time指运行前编译,预先编译:AOT编译能直接将源代码转化为机器码,内存占用低,启动速度快。无需runtime运行,直接将runtime静态链接至最终的程序中,但无运行时性能加成,不能根据程序运行情况做进一步的优化AOT缺点就是在程序运行前编译会使程序安装的时间增加。

JIT即时编译指的是在程序的运行过程中,将字节码转换为可在硬件上直接运行的机器码,并部署至托管环境中的过程。而AOT编译指的则是,在程序运行之前,便将字节码转换为机器码的过程。

.java -> .class -> (使用jaotc编译工具) -> .so(程序函数库,即编译好的可以供其他程序使用的代码和数据)

在这里插入图片描述

二、JIT

HotSpot虚拟机中,内置了两种JIT,分别为C1编译器和C2编译器,这两个编译器的编译过程是不一样的。

【1】C1编译器:C1编译器是一个简单快速的编译器,主要的关注点在于局部性的优化,适用于执行时间较短或对启动性能有要求的程序,也称为Client Compiler
【2】C2编译器:C2编译器是为长期运行的服务器端应用程序做性能调优的编译器,适用于执行时间较长或对峰值性能有要求的程序,也称为Server Compiler,例如,服务器上长期运行的Java应用对稳定运行就有一定的要求。JDK 6开始定义服务器级别的机器是至少有两个CPU2GB的物理内存,才开启C2
【3】分层编译:Java8中,默认开启分层编译,在1.8之前,分层编译默认是关闭的。在Java7之前,需要根据程序的特性来选择对应的JIT,虚拟机默认采用解释器和其中一个编译器配合工作。

分层编译将JVM的执行状态分为了5个层次:
【1】第 0 层:程序解释执行,默认开启性能监控功能Profiling,如果不开启,可触发第二层编译;
【2】第 1 层:可称为C1编译,将字节码编译为本地代码,进行简单、可靠的优化,不开启Profiling
【3】第 2 层:也称为C1编译,开启Profiling,仅执行带方法调用次数和循环回边执行次数profilingC1编译;
【4】第 3 层:也称为C1编译,执行所有带ProfilingC1编译;
【5】第 4 层:可称为C2编译,也是将字节码编译为本地代码,但是会启用一些编译耗时较长的优化,甚至会根据性能监控信息进行一些不可靠的激进优化。

mixed mode代表是默认的混合编译模式,除了这种模式外,我们还可以使用-Xint参数强制虚拟机运行于只有解释器的编译模式下,这时JIT完全不介入工作;也可以使用参数-Xcomp强制虚拟机运行于只有JIT的编译模式下。如下:

如果只想开启C2,可以关闭分层编译-XX:-TieredCompilation,如果只想用C1,可以在打开分层编译的同时,使用参数:-XX:TieredStopAtLevel=1

C1、C2C1+C2,分别对应clientserver和分层编译。C1编译速度快,优化方式比较保守;C2编译速度慢,优化方式比较激进。C1+C2在开始阶段采用C1编译,当代码运行到一定热度之后采用G2重新编译。

参数-XX:ReservedCodeCacheSize = N(其中N是为特定编译器提供的默认值)主要设置热点代码缓存codecache的大小。如果缓存不够,则JIT无法继续编译,并且会去优化,比如编译执行改为解释执行,由此,性能会降低。同时,可以通过java -XX:+PrintCodeCache查看codecache的使用情况:

C:/Users/Administrator> java -XX:+PrintCodeCache
CodeCache: size=245760Kb used=1165Kb max_used=1165Kb free=244594Kbbounds [0x000000010be1b000, 0x000000010c08b000, 0x00000011ae1b000]total_blobs=293 nmethods=48 adapters=159compilation: enable

相关参数:

参数默认值说明
-XX:InitialCodeCacheSize2555904(240M)默认的CodeCache区域大小,单位为字节
-XX:ReservedCodeCacheSize251658240(240M)CodeCache区域的最大值,单位为字节
-XX:CodeCacheExpansionSize65536(64K)CodeCache每次扩展大小,单位为字节
-XX:ExitOnFullCodeCachefalseCodeCache区域满了的时候是否退出JVM
-XX:UseCodeCacheFlushingfalse是否在关闭JIT编译前清除CodeCache
-XX:MinCodeCacheFlushingInterval30刷新CodeCache的最小时间间隔 ,单位为秒
-XX:CodeCacheMinimumFreeSpace512000CodeCache区域的剩余空间小于参数指定的值时停止JIT编译。剩余的空间不会再用来存放方法的本地代码, 可以存放本地方法适配器代码
-XX:CompileThreshold10000指定方法在在被JIT编译前被调用的次数
-XX:OnStackReplacePercentage140该值为用于计算是否触发OSR(OnStackReplace)编译的阈值

如何判断热点代码

【1】基于采样的热点探测: 主要是虚拟机会周期性的检查各个线程的栈顶,若某个或某些方法经常出现在栈顶,那这个方法就是“热点方法”。优点是实现简单。缺点是很难精确一个方法的热度,容易受到线程阻塞或外界因素的影响。
【2】基于计数器的热点探测(典型应用-Hotspot): 主要就是虚拟机给每一个方法甚至代码块建立了一个计数器,统计方法的执行次数,超过一定的阀值则标记为此方法为热点方法。Hotspot使用的基于计数器的热点探测方法。然后使用了两类计数器:方法调用计数器和回边计数器。当方法计数器和回边计数器之和超过方法计数器阈值时,就会触发JIT编译器。
【3】方法调用计数器: 方法调用计数器用于统计方法被调用的次数,默认阈值在C1模式下是1500次,在C2模式在是10000次,可通过-XX: CompileThreshold来设定;而在分层编译的情况下-XX: CompileThreshold指定的阈值将失效,此时将会根据当前待编译的方法数以及编译线程数来动态调整。
【4】回边计数器: 回边计数器用于统计一个方法中循环体代码执行的次数,在字节码中遇到控制流向后跳转的指令称为“回边”Back Edge,该值用于计算是否触发C1编译的阈值,在不开启分层编译的情况下,C1默认为13995C2默认为10700,可通过-XX: OnStackReplacePercentage=N来设置;而在分层编译的情况下,-XX: OnStackReplacePercentage指定的阈值同样会失效,此时将根据当前待编译的方法数以及编译线程数来动态调整。

回边计数器阈值计算规则:
1、C1模式下:CompileThreshold*OnStackReplacePercentage/100;即:方法调用计数器阈值*OSR比率/100;
2、C2模式下:(CompileThreshold)*(OnStackReplacePercentage-InterpreterProfilePercentage)/100;即:方法调用计数器阈值*(OSR比率 - 解释器监控比率)/100;

JIT优化

JIT编译运用了一些经典的编译优化技术来实现代码的优化:主要有两种:方法内联逃逸分析

【1】方法内联: 方法内联的优化行为就是把目标方法的代码复制到发起调用的方法之中,避免发生真实的方法调用。方法内联不仅可以消除调用本身带来的性能开销,还可以进一步触发更多的优化。

    private int add1(int s1, int s2, int s3, int s4) {return add2(s1+s2) + add2(s3+s4);}private int add2(int s1, int s2) {return s1+s2;}	// 方法内联后的代码private int add(int s1, int s2, int s3, int s4) {return s1+s2+s3+s4;}

提高方法内联的策略:调整参数;写小方法;使用staticfinal关键字,不用出现方法继承,没有额外的类型检查,就可能发生内联;

【2】锁消除: 如果是在单线程环境下,JIT编译会对这个对象的方法锁进行锁消除,jdk1.8默认开启。例如:

    //-XX:-EliminateLocks 先关闭锁消除, 再打开, 执行此段代码100万次查看差别很大public static String getString(String s1, String s2) {StringBuffer sb = new StringBuffer();sb.append(s1);sb.append(s2);return sb.toString();}

【3】标量替换: 逃逸分析证明一个对象不会被外部访问,如果这个对象可以被拆分的话,当程序真正执行的时候可能不创建这个对象,可以直接创建它的成员变量来代替,前提要开启逃逸分析(jdk1.8默认开启逃逸分析-XX:+DoEscapeAnalysis; -XX:+EliminateAllocations开启标量替换jdk1.8默认开启)。

public void foo() {Person info = new Person ();info.name = "queen";info.age= 18;	
}//逃逸分析后,代码会被优化(标量替换)为:
public void foo() {String name= "queen";int age= 18;
}

三、AOT

优点: Java虚拟机加载已经预编译成二进制库,可以直接执行。不必等待及时编译器的预热,减少Java应用给人带来“第一次运行慢” 的不良体验。在程序运行前编译,可以避免在运行时的编译性能消耗和内存消耗。可以在程序运行初期就达到最高性能,程序启动速度快。运行产物只有机器码,打包体积小。

缺点: 由于是静态提前编译,不能根据硬件情况或程序运行情况择优选择机器指令序列,理论峰值性能不如JIT,没有动态能力,同一份产物不能跨平台运行。第一种即时编译JIT是默认模式,Java Hotspot虚拟机使用它在运行时将字节码转换为机器码。后者提前编译AOT由新颖的GraalVM编译器支持,并允许在构建时将字节码直接静态编译为机器码。

现在正处于云原生,降本增效的时代,Java相比于GoRust等其他编程语言非常大的弊端就是启动编译和启动进程非常慢,这对于根据实时计算资源,弹性扩缩容的云原生技术相冲突,Spring6借助AOT技术在运行时内存占用低,启动速度快,逐渐的来满足Java在云原生时代的需求,对于大规模使用Java应用的商业公司可以考虑尽早调研使用JDK17,通过云原生技术为公司实现降本增效。

Graalvm

Spring6支持的AOT技术,底层通过GraalVM支持,Spring也对GraalVM本机映像提供了一流的支持。GraalVM是一种高性能JDK,旨在加速用Java和其他JVM语言编写的应用程序的执行,同时还为JavaScriptPython和许多其他流行语言提供运行时。GraalVM提供两种运行 Java应用程序的方法:在HotSpot JVM上使用Graal即时JIT编译器或作为提前AOT编译的本机可执行文件。GraalVM的多语言能力使得在单个应用程序中混合多种编程语言成为可能,同时消除了外部调用成本。GraalVMHotSpot Java虚拟机添加了一个用Java编写的高级即时JIT优化编译器。

GraalVM具有以下特性:
【1】一种高级优化编译器,它生成更快、更精简的代码,需要更少的计算资源;
【2】AOT本机图像编译提前将Java应用程序编译为本机二进制文件,立即启动,无需预热即可实现最高性能;
【3】Polyglot编程在单个应用程序中利用流行语言的最佳功能和库,无需额外开销;
【4】高级工具在Java和多种语言中调试、监视、分析和优化资源消耗;

Native Image

目前业界除了这种在JVM中进行AOT的方案,还有另外一种实现Java AOT的思路,那就是直接摒弃JVM,和C/C++一样通过编译器直接将代码编译成机器代码,然后运行。这无疑是一种直接颠覆Java语言设计的思路,那就是GraalVM Native Image。它通过C语言实现了一个超微缩的运行时组件Substrate VM,基本实现了JVM的各种特性,但足够轻量、可以被轻松内嵌,这就让Java语言和工程摆脱JVM的限制,能够真正意义上实现和C/C++一样的AOT编译。这一方案在经过长时间的优化和积累后,已经拥有非常不错的效果,基本上成为Oracle官方首推的Java AOT解决方案。
Native Image是一项创新技术,可将Java代码编译成独立的本机可执行文件或本机共享库。在构建本机可执行文件期间处理的Java字节码包括所有应用程序类、依赖项、第三方依赖库和任何所需的JDK类。生成的自包含本机可执行文件特定于不需要JVM的每个单独的操作系统和机器体系结构。

Native Image 构建过程

下载GraalVM 并配置环境变量:将JAVA_HOME修改为graalvm的位置,并将Path修改为graalvmbin位置。

变量名:JAVA_HOME
变量值:D:\graalvm-ce-java17-22.3.0

检查是否安装成功

C:/Users/Administrator>java -version
openjdk version "17.0.5" 2023-10-32
OpenJDK Runtime Environment GraalVM CE 22.3.0 (build 17.0.5+8-jvmci-22.3-b08)
OpenJDK 64-Bit Server VM GraalVM CE 22.3.0 (build 17.0.5+8-jvmci-22.3-b08, mixed mode, sharing)

通过gu install native-image安装native-image插件,并通过gu list检查版本

C:/Users/Administrator>gu install native-image
......
C:/Users/Administrator>gu list
......
native-image  22.3.0  Native Image   Early adopter

对比: 通过javac xx.javanative-image xx构建文件,其中包含了SVMJDK各种库后的大小,虽然相比C/C++的二进制文件来说体积偏大,但是对比完整JVM来说,可以说是已经是非常小了。

相比于使用JVM运行,Native Image的速度要快上不少,cpu占用也更低一些,从官方提供的各类实验数据也可以看出Native Image对于启动速度和内存占用带来的提升是非常显著的:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/116987.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【题解 单调队列优化dp】 简单的加法乘法计算题

题目描述: 分析: 由于对于每一步而言,我们都需要的是最小步数 所以我们很显然的可以写出一个dp方程: 设 f [ i ] f[i] f[i]表示达到i时的最小步数 我们有两种操作,也就是说我们可以通过一下两种方式转移过来&#xff…

解决使用WebTestClient访问接口报[185c31bb] 500 Server Error for HTTP GET “/**“

解决使用WebTestClient访问接口报[185c31bb] 500 Server Error for HTTP GET "/**" 问题发现问题解决 问题发现 WebTestClient 是 Spring WebFlux 框架中提供的用于测试 Web 请求的客户端工具。它可以不用启动服务器,模拟发送 HTTP 请求并验证服务器的响…

力扣刷题 day54:10-24

1.十进制整数的反码 每个非负整数 N 都有其二进制表示。例如, 5 可以被表示为二进制 "101",11 可以用二进制 "1011" 表示,依此类推。注意,除 N 0 外,任何二进制表示中都不含前导零。 二进制的反…

RHCSA常用命令总结

RHCSA回顾 1.Linux学习环境的安装部署 VMware虚拟机rhel9.x 磁盘容量:20GB cpu:1颗2核心 内存:2G 网卡:NAT 新CD/DVD设置镜像源文件 取消显示器的3d支持 (1)安装RHEL9 (2)组件:带有GUI的服务器 (3)分区…

了解OpenGL的Program Pipeline:

了解OpenGL的Program Pipeline: OpenGL是一个强大的图形渲染库,用于创建令人惊叹的实时图形应用程序。在OpenGL中,Program Pipeline(程序管线)是一个关键概念,它允许开发人员有效地管理多个着色器程序&…

CPU眼里的C/C++:1.2 查看变量和函数在内存中的存储位置

写一个很简单的 c 代码,打印一些“地址”, 也就是变量、函数的“存储位置”:当程序被加载到内存后,它们具体是存在哪里,可以用精确的数值来表示,这就是内存地址。 https://godbolt.org/z/Ghh9ThY5Y #inc…

CUDA纹理内存tex1D/tex2D/tex3D函数

CUDA的tex1D是用于从一维纹理中读取数据的函数。纹理是一种特殊的内存区域,可以用来存储图像、视频或其他数据。tex1D函数可以用于从纹理中读取数据,并将其传递给CUDA程序。 tex1D函数的语法如下: float tex1D(sampler_t sampler, float te…

PureFlash云原生存储部署方法

PureFlash云原生存储 PureFlash是一个开源存储系统,它能为云计算和传统应用提供块存储服务。PureFlash最显著的优势是其高性能,每节点能提供超过100万IOPS随机写IO。 PureFlash可以以云原生的方式部署,并为云原生应用提供持久存储。 PureFl…

Java基础篇 | Java8流式编程

✅作者简介:大家好,我是Leo,热爱Java后端开发者,一个想要与大家共同进步的男人😉😉 🍎个人主页:Leo的博客 💞当前专栏: Java从入门到精通 ✨特色专栏&#xf…

自然语言处理---Transformer模型

Transformer概述 相比LSTM和GRU模型,Transformer模型有两个显著的优势: Transformer能够利用分布式GPU进行并行训练,提升模型训练效率。 在分析预测更长的文本时,捕捉间隔较长的语义关联效果更好。 Transformer模型的作用 基于seq…

Ai写作创作系统ChatGPT网站源码+图文搭建教程+支持GPT4.0+支持ai绘画(Midjourney)/支持OpenAI GPT全模型+国内AI全模型

一、AI创作系统 SparkAi创作系统是基于OpenAI很火的ChatGPT进行开发的Ai智能问答系统AI绘画系统,支持OpenAI GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美,可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭建部署…

一文了解AIGC与ChatGPT

关注微信公众号掌握更多技术动态 --------------------------------------------------------------- 一、AIGC简介 1.AIGC基础 (1)AIGC是什么 AIGC是人工智能图形计算的缩写,是一种基于图形处理器(GPU)的计算技术,可以加速各种…

业务出海、高效传输、动态加速,尽在云栖大会「CDN与边缘计算」专场

2023杭州云栖大会,即将热力来袭。 一场云计算盛会,500前沿话题,3000科技展品,与阿里云一起,共赴72小时的Tech沉浸之旅。 今日,「CDN与边缘计算」Tech专场,重磅议题抢先知晓! 01 「…

Elasticsearch的增删查改详细操作

目录标题 一、创建索引二、查看索引三、修改索引四、删除索引五、向索引增加数据 一、创建索引 单独创建索引 PUT /test1 # test1 为索引名称自定义{"settings":{ # 创建index 需要有效的xcontent字节及Json格式 否则创建不成功 "index":{"number_…

log函数解释

log函数是指数函数y bx 的反函数,用于求数字以某个数为底的对数。log函数的定义:设b>0,b≠1,对于任意实数x > 0,如果存在唯一的实数y,使得 b^y x,则称y为以b为底x的对数,记为:y log_b(x)这里b称为对数的底数。对数运算的底数通常取10和e。常见的对数运算有:1. 常用对数…

华为OD 污染水域(200分)【java】A卷+B卷

华为OD统一考试A卷+B卷 新题库说明 你收到的链接上面会标注A卷还是B卷。目前大部分收到的都是B卷。 B卷对应往年部分考题以及新出的题目。 我将持续更新最新题目 我精选了一部分题目免费分享给大家,可前往夸克网盘转存,请点击以下链接进入: 我用夸克网盘分享了「华为OD题库J…

前后端交互—跨域与HTTP

跨域 代码下载 同源策略 同源策略(英文全称 Same origin policy)是浏览器提供的一个安全功能。 MDN 官方给定的概念:同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这 是一个用于隔离潜在恶意文件的重要安全机制。 通俗的理解:浏览器规定&a…

测试C#调用Windows Media Player组件

新建基于.net framework的Winform项目,可以通过添加引用的方式选择COM组件中的Windows Media Player组件,如下图所示:   也可以在VS2022的工具箱空白处点右键,选择“选择项…”菜单。   在弹出的选择工具箱项窗口中&#xf…

我想要一个勋章

目录 一、背景二、过程三、总结 一、背景 十年前结缘,也许是冥冥中自有天注定,注定要给自己多加一个今天的节日。 二、过程 一个勋章,一个有意义的标志。 一个勋章,一个时间轮上的帧。 一个勋章,一个二进制的节点。…

小样本学习(2)--LibFewShot使用

目录 一、LibFewShot安装 1、LibFewShot代码仓库 2、配置环境 3、测试安装是否正确 二、LibFewShot结构 1、config文件夹 2、core文件夹 3、reproduce文件夹 4、results文件夹 三、如何训练自己的数据集 1、调用主配置文件 2、修改主配置文件 一、LibFewShot安装…