10-让Java性能提升的JIT深度剖析

文章目录

  • JVM的语言无关性
  • 解释执行与JIT
  • C1、C2与Graal编译器
    • C1编译器
    • C2编译器
  • 分层编译(了解即可)
  • 热点代码
  • 热点探测
    • 方法调用计数器
    • 回边计数器
  • 编译优化技术
    • 方法内联
    • 锁消除
    • 标量替换
    • 逃逸分析技术
      • 逃逸分析的原理
      • 逃逸分析

JVM的语言无关性

跨语言(语言无关性):JVM只识别字节码,所以JVM其实跟语言是解耦的,也就是没有直接关联,JVM运行不是翻译Java文件,而是识别class文件,这个一般称之为字节码。还有像Groovy 、Kotlin、Scala等等语言,它们其实也是编译成字节码,所以它们也可以在JVM上面跑,这个就是JVM的跨语言特征。Java的跨语言性一定程度上奠定了非常强大的java语言生态圈。
在这里插入图片描述

解释执行与JIT

在这里插入图片描述

Java程序在运行的时候,主要就是执行字节码指令,一般这些指令会按照顺序解释执行,这种就是解释执行。
在这里插入图片描述

但是那些被频繁调用的代码,比如调用次数很高或者在 for 循环里的那些代码,如果按照解释执行,效率是非常低的。(这个就是Java以前被C、C++开发者吐槽慢的原因)
以上的这些代码称为热点代码。所以,为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化。
完成这个任务的编译器,就称为即时编译器(Just In Time Compiler),简称 JIT 编译器

C1、C2与Graal编译器

在这里插入图片描述

在JDK1.8中 HotSpot 虚拟机中,内置了两个 JIT,分别为 C1 编译器和 C2 编译器。

C1编译器

C1 编译器是一个简单快速的编译器,主要的关注点在于局部性的优化,适用于执行时间较短或对启动性能有要求的程序,例如,GUI 应用对界面启动速度就有一定要求,C1也被称为 Client Compiler。
C1编译器几乎不会对代码进行优化

C2编译器

C2 编译器是为长期运行的服务器端应用程序做性能调优的编译器,适用于执行时间较长或对峰值性能有要求的程序。根据各自的适配性,这种即时编译也被称为Server Compiler。
但是C2代码已超级复杂,无人能维护!所以才会开发Java编写的Graal编译器取代C2(JDK10开始)

分层编译(了解即可)

在 Java7之前,需要根据程序的特性来选择对应的 JIT,虚拟机默认采用解释器和其中一个编译器配合工作。
Java7及以后引入了分层编译,这种方式综合了 C1 的启动性能优势和 C2 的峰值性能优势,当然我们也可以通过参数强制指定虚拟机的即时编译模式。
在 Java8 中,默认开启分层编译。
通过 java -version 命令行可以直接查看到当前系统使用的编译模式(默认分层编译)
在这里插入图片描述

使用“-Xint”参数强制虚拟机运行于只有解释器的编译模式
在这里插入图片描述

使用“-Xcomp”强制虚拟机运行于只有 JIT 的编译模式下
在这里插入图片描述

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

热点代码

热点代码,就是那些被频繁调用的代码,比如调用次数很高或者在 for 循环里的那些代码。这些再次编译后的机器码会被缓存起来,以备下次使用,但对于那些执行次数很少的代码来说,这种编译动作就纯属浪费。
JVM提供了一个参数“-XX:ReservedCodeCacheSize”,用来限制 CodeCache 的大小。也就是说,JIT 编译后的代码都会放在 CodeCache 里。
如果这个空间不足,JIT 就无法继续编译,编译执行会变成解释执行,性能会降低一个数量级。同时,JIT 编译器会一直尝试去优化代码,从而造成了 CPU 占用上升。
通过 java -XX:+PrintFlagsFinal –version查询:
在这里插入图片描述

热点探测

在 HotSpot 虚拟机中的热点探测是 JIT 优化的条件,热点探测是基于计数器的热点探测,采用这种方法的虚拟机会为每个方法建立计数器统计方法的执行次数,如果执行次数超过一定的阈值就认为它是“热点方法”
虚拟机为每个方法准备了两类计数器:方法调用计数器(Invocation Counter)回边计数器(Back Edge Counter)。在确定虚拟机运行参数的前提下,这两个计数器都有一个确定的阈值,当计数器超过阈值溢出了,就会触发 JIT 编译。

方法调用计数器

用于统计方法被调用的次数,方法调用计数器的默认阈值在客户端模式下是 1500 次,在服务端模式下是 10000 次(我们用的都是服务端,java –version查询),可通过 -XX: CompileThreshold 来设定
在这里插入图片描述

通过 java -XX:+PrintFlagsFinal –version查询
在这里插入图片描述

回边计数器

用于统计一个方法中循环体代码执行的次数,在字节码中遇到控制流向后跳转的指令称为“回边”(Back Edge),该值用于计算是否触发 C1 编译的阈值,在不开启分层编译的情况下,在服务端模式下是10700
怎么算的呢!参考以下公式(有兴趣可了解):
回边计数器阈值 =方法调用计数器阈值(CompileThreshold)×(OSR比率(OnStackReplacePercentage)-解释器监控比率(InterpreterProfilePercentage)/100
通过 java -XX:+PrintFlagsFinal –version查询先关参数:
在这里插入图片描述
在这里插入图片描述

其中OnStackReplacePercentage默认值为140,InterpreterProfilePercentage默认值为33,如果都取默认值,那Server模式虚拟机回边计数器的阈值为10700.
回边计数器阈值 =10000×(140-33)=10700

编译优化技术

JIT 编译运用了一些经典的编译优化技术来实现代码的优化,即通过一些例行检查优化,可以智能地编译出运行时的最优性能代码.

方法内联

方法内联的优化行为就是把目标方法的代码复制到发起调用的方法之中,避免发生真实的方法调用
例如以下方法:
在这里插入图片描述

最终会被优化为:
在这里插入图片描述

JVM 会自动识别热点方法,并对它们使用方法内联进行优化。
我们可以通过 -XX:CompileThreshold 来设置热点方法的阈值。
但要强调一点,热点方法不一定会被 JVM 做内联优化,如果这个方法体太大了,JVM 将不执行内联操作
而方法体的大小阈值,我们也可以通过参数设置来优化:
经常执行的方法,默认情况下,方法体大小小于 325 字节的都会进行内联,我们可以通过 -XX:FreqInlineSize=N 来设置大小值;
在这里插入图片描述

不是经常执行的方法,默认情况下,方法大小小于 35 字节才会进行内联,我们也可以通过 -XX:MaxInlineSize=N 来重置大小值。
在这里插入图片描述

代码演示
在这里插入图片描述

设置 VM 参数:-XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions
-XX:+PrintInlining
-XX:+PrintCompilation //在控制台打印编译过程信息
-XX:+UnlockDiagnosticVMOptions //解锁对JVM进行诊断的选项参数。默认是关闭的,开启后支持一些特定参数对JVM进行诊断
-XX:+PrintInlining //将内联方法打印出来
在这里插入图片描述

如果循环太少,则不会触发方法内联
在这里插入图片描述

热点方法的优化可以有效提高系统性能,一般我们可以通过以下几种方式来提高方法内联:

  • 通过设置 JVM 参数来减小热点阈值或增加方法体阈值,以便更多的方法可以进行内联,但这种方法意味着需要占用更多地内存;
  • 在编程中,避免在一个方法中写大量代码,习惯使用小方法体
  • 尽量使用 final、private、static 关键字修饰方法,编码方法因为继承,会需要额外的类型检查。

锁消除

在非线程安全的情况下,尽量不要使用线程安全容器,比如 StringBuffer。 由于 StringBuffer 中的 append 方法被 Synchronized 关键字修饰,会使用到锁,从而导致性能下降。
在这里插入图片描述

但实际上,在以下代码测试中,StringBuffer 和 StringBuilder 的性能基本没什么区别。这是因为在局部方法中创建的对象只能被当前线程访问,无法被其它线程访问,这个变量的读写肯定不会有竞争,这个时候 JIT 编译会对这个对象的方法锁进行锁消除
在这里插入图片描述
在这里插入图片描述

我们把锁消除关闭—测试发现性能差别有点大

-XX:+EliminateLocks  #开启锁消除(jdk1.8默认开启,其它版本未测试)
-XX:-EliminateLocks  #关闭锁消除

在这里插入图片描述

标量替换

逃逸分析证明一个对象不会被外部访问,如果这个对象可以被拆分的话,当程序真正执行的时候可能不创建这个对象,而直接创建它的成员变量来代替。将对象拆分后,可以分配对象的成员变量在栈或寄存器上,原本的对象就无需分配内存空间了。这种编译优化就叫做标量替换(前提是需要开启逃逸分析)。
在这里插入图片描述

-XX:+DoEscapeAnalysis   #开启逃逸分析(jdk1.8默认开启)
-XX:-DoEscapeAnalysis   #关闭逃逸分析-XX:+EliminateAllocations  #开启标量替换(jdk1.8默认开启)
-XX:-EliminateAllocations  #关闭标量替换

逃逸分析技术

在这里插入图片描述

逃逸分析的原理

分析对象动态作用域,当一个对象在方法中定义后,它可能被外部方法所引用。
比如:调用参数传递到其他方法中,这种称之为方法逃逸。甚至还有可能被外部线程访问到,例如:赋值给其他线程中访问的变量,这个称之为线程逃逸。
从不逃逸到方法逃逸到线程逃逸,称之为对象由低到高的不同逃逸程度。
如果确定一个对象不会逃逸出线程之外,那么让对象在栈上分配内存可以提高JVM的效率。
当然逃逸分析技术属于JIT的优化技术,所以必须要符合热点代码,JIT才会优化,另外对象如果要分配到栈上,需要将对象拆分,这种编译优化就叫做标量替换技术。
如下图中foo方法如果使用标量替换的话,那么最后执行的话就是foo1方法的效果。
在这里插入图片描述

逃逸分析代码示例

在这里插入图片描述

这段代码在调用的过程中Myboject这个对象属于不可逃逸,JVM可以做栈上分配,所以运行速度非常快!
JVM默认会做逃逸分析、会进行标量替换,会进行栈上分配。
在这里插入图片描述

然后关闭逃逸分析

-XX:-DoEscapeAnalysis

在这里插入图片描述

然后关闭标量替换

 -XX:-EliminateAllocations

在这里插入图片描述

测试结果可见,开启逃逸分析对代码的执行性能有很大的影响!那为什么有这个影响?

逃逸分析

如果是逃逸分析出来的对象可以在栈上分配的话,那么该对象的生命周期就跟随线程了,就不需要垃圾回收,如果是频繁的调用此方法则可以得到很大的性能提高。
采用了逃逸分析后,满足逃逸的对象在栈上分配
没有开启逃逸分析,对象都在堆上分配,会频繁触发垃圾回收(垃圾回收会影响系统性能),导致代码运行慢

代码验证
开启GC打印日志

-XX:+PrintGC

开启逃逸分析
在这里插入图片描述

可以看到没有GC日志(因为进行了栈上分配)
关闭逃逸分析
在这里插入图片描述

可以看到关闭了逃逸分析,JVM在频繁的进行垃圾回收(GC),正是这一块的操作导致性能有较大的差别。

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

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

相关文章

OpenHarmony之内核层解析~

OpenHarmony简介 技术架构 OpenHarmony整体遵从分层设计,从下向上依次为:内核层、系统服务层、框架层和应用层。系统功能按照“系统 > 子系统 > 组件”逐级展开,在多设备部署场景下,支持根据实际需求裁剪某些非必要的组件…

平衡二叉树的构建(递归

目录 1.概念:2.特点:3.构建方法:4.代码:小结: 1.概念: 平衡二叉树(Balanced Binary Tree),也称为AVL树,是一种二叉树,它满足每个节点的左子树和右…

BDD - Python Behave Runner Script

BDD - Python Behave Runner Script 引言Runner Scriptsubprocess.run 调用 Behave 命令行调用 Behave 提供的 API behave_main 引言 通过终端命令执行 Behave 测试用例,有时 IDE 重启了,还得重新敲一遍命令,很是麻烦,说实话我都…

单例模式(C++实现)

RAII运用 只能在栈上创建对象 只能在堆上创建的对象 单例模式 设计模式 懒汉模式 解决线程安全 优化 饿汉模式 饿汉和懒汉的区别 线程安全与STL与其他锁

Hadoop入门学习笔记——四、MapReduce的框架配置和YARN的部署

视频课程地址:https://www.bilibili.com/video/BV1WY4y197g7 课程资料链接:https://pan.baidu.com/s/15KpnWeKpvExpKmOC8xjmtQ?pwd5ay8 Hadoop入门学习笔记(汇总) 目录 四、MapReduce的框架配置和YARN的部署4.1. 配置MapReduce…

微擎模块 出现Error: template source ‘common/message’ is not exist!解决方法

今天有会员反馈微课堂分销中心打不开,错误提示模板找不到:Error: template source ‘common/message’ is not exist!,看了下这模板应该微擎框架通用的,进公众号会员管理-会员中心网址居然也打不开,提示一样的错误&…

【hacker送书第11期】Python数据分析从入门到精通

探索数据世界,揭示未来趋势 《Python数据分析从入门到精通》是你掌握Python数据分析的理想选择。本书深入讲解核心工具如pandas、matplotlib和numpy,助您轻松处理和理解复杂数据。 通过matplotlib、seaborn和创新的pyecharts,本书呈现生动直…

5g消息-5G时代短信升级-富媒体智能交互-互联网新入口

在5G时代,运营商和各大手机厂商都在积极推进5G消息的商用,基于短信入口的富媒体消息应用在近两年得到快速发展,并在企业端形成了广泛应用。 作为5G时代的数字原生应用,5G消息支持用户通过文字、图片、音频、视频、位置等富媒体方式…

GIT具体配置步骤详解

GIT配置具体步骤如下 SDK 使用 Repo 工具管理,拉取 SDK 需要配置安装 Repo 工具。 Repo is a tool built on top of Git. Repo helps manage many Git repositories, does the uploads to revision control systems, and automates parts of the development workf…

SpringBoot集成opencc4j实现繁体中文转为简体中文

背景 繁体中文转为简体中文的需求非常常见,特别是在中文语境下的文本处理和翻译应用中。有很多现成的工具和库可以实现这个功能,比如 OpenCC 、 HanLP 等。从网上下载的 MySQL 版诗词数据库中的诗词数据都是繁体字,这里使用 SpringBoot 集成…

Apache Flink 进阶教程(七):网络流控及反压剖析

目录 前言 网络流控的概念与背景 为什么需要网络流控 网络流控的实现:静态限速 网络流控的实现:动态反馈/自动反压 案例一:Storm 反压实现 案例二:Spark Streaming 反压实现 疑问:为什么 Flink(bef…

物联网产品设计,聊聊设备OTA的升级

物联网产品设计部分的OTA设备固件是一个非常重要的部分,能够实现升级用户服务、保障系统安全等功能。 在迅速变化和发展的物联网市场,新的产品需求不断涌现,因此对于智能硬件设备的更新需求就变得空前高涨,设备不再像传统设备一样…

unity HoloLens2开发,使用Vuforia识别实体 触发交互(二)(有dome)

提示:文章有错误的地方,还望诸位大神不吝指教! 文章目录 前言一、打包到HoloLens二、Vuforia相关1.配置识别框2.制作一个半透明识别框:3.设置如下4.问题 四 HoloLens2 问题总结 前言 我使用的utniy 版本:Unity 2021.3…

产品原型设计软件 Axure RP 9 mac支持多人写作设计

axure rp 9 mac是一款产品原型设计软件,它可以让你在上面任意构建草图、框线图、流程图以及产品模型,还能够注释一些重要地方,axure rp汉化版可支持同时多人写作设计和版本管理控制,这款交互式原型设计工具可以帮助设计者制作出高…

Jenkins自动化构建打包,部署

1.环境准备 上传jdk,maven和tomcat的包,解压到/usr/local下并配置环境变量。 配置jdk [rootserver04 ~]# vim /etc/profile.d/java.sh JAVA_HOME/usr/local/java export PATH$JAVA_HOME/bin:$PATH##加载环境变量 [rootserver04 ~]# source /etc/profi…

由于被认为是客户端对错误(例如:畸形的请求语法、无效的请求信息帧或者虚拟的请求路由),服务器无法或不会处理当前请求。

问题描述: 由于被认为是客户端对错误(例如:畸形的请求语法、无效的请求信息帧或者虚拟的请求路由),服务器无法或不会处理当前请求。 在实现向数据库中添加记录时,请求发送无效,参数也未传递到控…

HAL库的常用库函数(根据学习而更新)

目录 一、常用的GPIO相关HAL库函数 1、GPIO的初始化 2、配置GPIO引脚输出电平 3、切换指定引脚的电平,电平的翻转 4、读取指定GPIO引脚的电平 5、结构体 GPIO_InitTypeDef (引脚)定义: 6、高低电平的表示 7、延时函数&…

如何使用内网穿透工具实现Java远程连接本地Elasticsearch搜索分析引擎

文章目录 前言1. Windows 安装 Cpolar2. 创建Elasticsearch公网连接地址3. 远程连接Elasticsearch4. 设置固定二级子域名 前言 简单几步,结合Cpolar 内网穿透工具实现Java 远程连接操作本地分布式搜索和数据分析引擎Elasticsearch。 Cpolar内网穿透提供了更高的安全性和隐私保…

【计算机视觉中的多视图几何系列】深入浅出理解针孔相机模型

温故而知新,可以为师矣! 一、参考资料 《计算机视觉中的多视图几何-第五章》-Richard Hartley, Andrew Zisserman. 二、针孔模型相关介绍 1. 重要概念 1.1 投影中心/摄像机中心/光心 投影中心称为摄像机中心,也称为光心。投影中心位于一…

50个免费的 AI 工具,提升工作效率(附网址)

上次我们已经介绍了20个精选的提高工作效率的免费AI工具,但如果你觉得这些AI工具还不过瘾的话,想进一步成为职场中最了解AI的人,本文将汇总介绍免费最新的50个AI工具。 DeepSwap DeepSwap 是一个基于 AI 的工具,适用于想要制作令人…