JVM:即时编译器,C2 Compiler,堆外内存排查

1,即时编译器

1.1,基本概念

常见的编译型语言如C++,通常会把代码直接编译成CPU所能理解的机器码来运行。而Java为了实现“一次编译,处处运行”的特性,把编译的过程分成两部分,首先它会先由javac编译成通用的中间形式——字节码(实现跨平台),然后再由解释器逐条将字节码解释为机器码来执行。所以在性能上,Java通常不如C++这类编译型语言。

为了优化Java的性能 ,JVM在解释器之外引入了即时(Just In Time)编译器:当程序运行时,解释器首先发挥作用,代码可以直接执行。随着时间推移,即时编译器逐渐发挥作用,把越来越多的代码编译优化成本地代码,来获取更高的执行效率。解释器这时可以作为编译运行的降级手段,在一些不可靠的编译优化出现问题时,再切换回解释执行,保证程序可以正常运行。即时编译器极大地提高了Java程序的运行速度,而且跟静态编译相比,即时编译器可以选择性地编译热点代码,省去了很多编译时间,也节省很多的空间。

举例来说:刚学骑自行车时就像JVM第一次解释执行字节码,需要更多的时间和精力。但通过不断练习(JIT编译),你逐渐掌握了技巧,变得更高效。

解释性比编译性慢的原因:编译器在编译过程中通常会考虑很多因素。比如:汇编指令的顺序。假设我们要将两个寄存器的值进行相加,执行这个操作一般只需要一个CPU周期;但是在相加之前需要将数据从内存读到寄存器中,这个操作是需要多个CPU周期的。编译器一般可以做到,先启动数据加载操作,然后执行其它指令,等数据加载完成后,再执行相加操作。由于解释器在解释执行的过程中,每次只能看到一行代码,所以很难生成上述这样的高效指令序列。而编译器可以事先看到所有代码,因此,一般来说,解释性代码比编译性代码要慢。

1.2,底层原理

Java的执行过程整体可以分为两个部分,第一步由javac将源码编译成字节码,在这个过程中会进行词法分析、语法分析、语义分析,编译原理中这部分的编译称为前端编译。接下来无需编译直接逐条将字节码解释执行,在解释执行的过程中,虚拟机同时对程序运行的信息进行收集,在这些信息的基础上,编译器会逐渐发挥作用,它会进行后端编译——把字节码编译成机器码,但不是所有的代码都会被编译,只有被JVM认定为的热点代码,才可能被编译。

怎么样才会被认为是热点代码呢?JVM中会设置一个阈值,当方法或者代码块的在一定时间内的调用次数超过这个阈值时就会被编译,存入codeCache中。当下次执行时,再遇到这段代码,就会从codeCache中读取机器码,直接执行,以此来提升程序运行的性能。

1.3,编译器种类

【Client Compiler】注重启动速度和局部的优化。HotSpot VM带有一个 Client Compiler C1编译器。这种编译器启动速度快,但是性能比较Server Compiler来说会差一些。C1会做三件事:

  • 局部简单可靠的优化,比如字节码上进行的一些基础优化,方法内联、常量传播等,放弃许多耗时较长的全局优化。
  • 将字节码构造成高级中间表示(High-level Intermediate Representation,以下称为HIR),HIR与平台无关,通常采用图结构,更适合JVM对程序进行优化。
  • 最后将HIR转换成低级中间表示(Low-level Intermediate Representation,以下称为LIR),在LIR的基础上会进行寄存器分配、窥孔优化(局部的优化方式,编译器在一个基本块或者多个基本块中,针对已经生成的代码,结合CPU自己指令的特点,通过一些认为可能带来性能提升的转换规则或者通过整体的分析,进行指令转换,来提升代码性能)等操作,最终生成机器码。

【Server Compiler】更加关注全局的优化,性能会更好,但由于会进行更多的全局分析,所以启动速度会变慢。Server Compiler主要关注一些编译耗时较长的全局优化,甚至会还会根据程序运行的信息进行一些不可靠的激进优化。这种编译器的启动时间长,适用于长时间运行的后台程序,它的性能通常比Client Compiler高30%以上。目前,Hotspot虚拟机中使用的Server Compiler有两种:

  • 【C2 Compiler】在Hotspot VM中,默认的Server Compiler是C2编译器。
  • Graal Compiler】从JDK 9开始,Hotspot VM中集成了一种新的Server Compiler,Graal编译器。

【C2 Compiler】C2编译器在进行编译优化时,会使用一种控制流与数据流结合的图数据结构,称为Ideal Graph。Ideal Graph表示当前程序的数据流向和指令间的依赖关系,依靠这种图结构,某些优化步骤(尤其是涉及浮动代码块的那些优化步骤)变得不那么复杂。

Ideal Graph的构建是在解析字节码的时候,根据字节码中的指令向一个空的Graph中添加节点,Graph中的节点通常对应一个指令块,每个指令块包含多条相关联的指令,JVM会利用一些优化技术对这些指令进行优化,比如Global Value Numbering、常量折叠等,解析结束后,还会进行一些死代码剔除的操作。生成Ideal Graph后,会在这个基础上结合收集的程序运行信息来进行一些全局的优化,这个阶段如果JVM判断此时没有全局优化的必要,就会跳过这部分优化。

无论是否进行全局优化,Ideal Graph都会被转化为一种更接近机器层面的MachNode Graph,最后编译的机器码就是从MachNode Graph中得的,生成机器码前还会有一些包括寄存器分配、窥孔优化等操作。

Graal Compiler】相比C2编译器,Graal有这样几种关键特性:

  • JVM会在解释执行的时候收集程序运行的各种信息,然后编译器会根据这些信息进行一些基于预测的激进优化,比如分支预测,根据程序不同分支的运行概率,选择性地编译一些概率较大的分支。Graal比C2更加青睐这种优化,所以Graal的峰值性能通常要比C2更好。
  • 使用Java编写,对于Java语言,尤其是新特性,比如Lambda、Stream等更加友好。
  • 更深层次的优化,比如虚函数的内联、部分逃逸分析等。

Graal编译器可以通过Java虚拟机参数-XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler启用。当启用时,它将替换掉HotSpot中的C2编译器,并响应原本由C2负责的编译请求。

1.4,分层编译

Java 7开始引入了分层编译的概念,它结合了C1和C2的优势,追求启动速度和峰值性能的一个平衡。从JDK 8开始,JVM默认开启分层编译。分层编译将JVM的执行状态分为了五个层次。五个层级分别是:

  • 解释执行。
  • 执行不带profiling的C1代码。
  • 执行仅带方法调用次数以及循环回边执行次数profiling的C1代码。
  • 执行带所有profiling的C1代码。
  • 执行C2代码。

profiling就是收集能够反映程序执行状态的数据。其中最基本的统计数据就是方法的调用次数,以及循环回边的执行次数。

通常情况下,C2代码的执行效率要比C1代码的高出30%以上。C1层执行的代码,按执行效率排序从高至低则是1层>2层>3层。这5个层次中,1层和4层都是终止状态,当一个方法到达终止状态后,只要编译后的代码并没有失效,那么JVM就不会再次发出该方法的编译请求的。服务实际运行时,JVM会根据服务运行情况,从解释执行开始,选择不同的编译路径,直到到达终止状态。

  • 图中第①条路径,代表编译的一般情况,热点方法从解释执行到被3层的C1编译,最后被4层的C2编译。
  • 如果方法比较小(比如Java服务中常见的getter/setter方法),3层的profiling没有收集到有价值的数据,JVM就会断定该方法对于C1代码和C2代码的执行效率相同,就会执行图中第②条路径。在这种情况下,JVM会在3层编译之后,放弃进入C2编译,直接选择用1层的C1编译运行。
  • 在C1忙碌的情况下,执行图中第③条路径,在解释执行过程中对程序进行profiling ,根据信息直接由第4层的C2编译。
  • 前文提到C1中的执行效率是1层>2层>3层,第3层一般要比第2层慢35%以上,所以在C2忙碌的情况下,执行图中第④条路径。这时方法会被2层的C1编译,然后再被3层的C1编译,以减少方法在3层的执行时间。
  • 如果编译器做了一些比较激进的优化,比如分支预测,在实际运行时发现预测出错,这时就会进行反优化,重新进入解释执行,图中第⑤条执行路径代表的就是反优化。

总的来说,C1的编译速度更快,C2的编译质量更高,分层编译的不同编译路径,也就是JVM根据当前服务的运行情况来寻找当前服务的最佳平衡点的一个过程。

2,C2 Compiler 占用高内存分析

2.1,原因解释

C2 Compiler 是JVM在server模式下字节码编译器,JVM启动的时候所有代码都处于解释执行模式,当某些代码被执行到一定阈值次数,这些代码(称为热点代码)就会被 C2 Compiler编译成机器码,编译成机器码后执行效率会得到大幅提升。流量进来后,大部分代码成为热点代码,这个过程中C2 Compiler需要频繁占用CPU来运行,当大部分热点代码被编译成机器代码后,C2 Compiler就不再长期占用CPU了,这个过程也可以看作抖动。

2.2,解决方案(不放弃C2)

【方案一】最直接有效的方法是“预热(warm up)”可以使用Jmeter等压测工具模拟线上访问流量,让C2 Compiler预先将热点代码编译成机器码, 减少对正式环境流量的影响。

【方案二】设置JVM启动参数:-XX:CICompilerCount=threads。默认是2, 可以设置4或6。在默认值下抖动时CPU已经满载,设置成更多的线程也不一定起作用,但对于CPU“高而不满”的情况会有用,能减少抖动时间。

【方案三】修改codeCache的默认大小:-XX:ReservedCodeCacheSize=300M

【方案四】关闭分层编译:-XX:-TieredCompilation -server

C2 CompilerThread9 长时间占用CPU解决方案 - 沧海一滴 - 博客园

3,堆外内存泄漏排查

3.1,生产问题

有个系统从JDK8升级到JDK21,垃圾回收算法为分代ZGC算法,看上去该算法会倾向于在堆内存高使用率时才触发GC,当使用率有毛刺达到100%才触发GC时,使得JVM堆+堆外+元空间+容器本身内存使用,超过容器物理内存4G,达到了K8S的pod阈值,直接kill调了pod容器。

3.2,解决方案 

ZGC的总体逻辑是这样的,它其实并非降内存消耗,只是降停顿,所以其实需要额外的内存空间来完成这件事。 JVM 总内存 ≈ 堆内存(Xmx) + 堆外内存 + 元空间 + 线程栈数量 * Xss + 额外内存;

  • 这个额外内存是需要留下来的。按照你的xmx=1.5,额外内存基本上最好得在0.8+(50%以上)。
  • xms!=xmx 这个设置是出现毛刺的原因之一,一般会设置一样或者差不多。
  • 4g内存暂时无法变更:
# 固定堆内存为 2GB
-Xms2g -Xmx2g
# 提前触发 ZGC 回收
-XX:SoftMaxHeapSize=1.7g
# 限制堆外内存为 256MB
-XX:MaxDirectMemorySize=256m
# 限制元空间为 256MB
-XX:MaxMetaspaceSize=256m
# 限制线程栈大小为 512KB
-Xss512k
# 限制 JVM 总内存为容器的 85%
-XX:MaxRAMPercentage=85
  • 4g有希望换到8g:
# 固定堆内存为 4GB
-Xms4g -Xmx4g
# 提前触发 ZGC 回收
-XX:SoftMaxHeapSize=3.2g
# 限制堆外内存为 1GB
-XX:MaxDirectMemorySize=1g
# 限制元空间为 512MB
-XX:MaxMetaspaceSize=512m
# 限制线程栈大小为 512KB
-Xss512k
# 限制 JVM 总内存为容器的 90%
-XX:MaxRAMPercentage=90

思路也很简单,额外留的内存最好大于Xmx的50%。然后把用SoftMaxHeapSize做一下平滑GC,相当于提前触发下GC,避免到峰值时突然来了大对象,而又没有GC出空间导致一下冲破最大限制。

3.3,排查思路

一次完整的JVM堆外内存泄漏故障排查记录 - 蛮三刀酱 - 博客园

调试排错 - Java 内存分析之堆外内存 | Java 全栈知识体系

记一次堆外内存泄漏排查过程 - AI乔治 - 博客园

Java 堆外内存排查 - 莫那·鲁道的技术博客

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

rocylinux9.4安装prometheus监控

一.上传软件包 具体的软件包如下,其中kubernetes-mixin是下载的监控kubernetes的一些监控规则、dashbaordd等。 二.Prometheus配置 1.promethes软件安装 #解压上传后的软件包 [rootlocalhost ] cd /opt [rootlocalhost opt]# tar xf prometheus-2.35.3.linux-amd…

FreeRTOS之链表源码分析

文章目录 前言一、结构体1、链表List_t2、链表项xLIST_ITEM3、头节点xMINI_LIST_ITEM4、链表示意图 二、函数分析1、初始化函数vListInitialise2、初始化链表项vListInitialiseItem3、链表尾部添加节点vListInsertEnd4、按序插入节点vListInsert5、删除节点uxListRemove 总结 前…

预测未来 | MATLAB实现Transformer时间序列预测未来

预测未来 | MATLAB实现Transformer时间序列预测未来 预测效果 基本介绍 1.Matlab实现Transformer时间序列预测未来; 2.运行环境Matlab2023b及以上,data为数据集,单变量时间序列预测; 3.递归预测未来数据,可以控制预…

怎么样才算得上熟悉高并发编程?

提到并发编程很多人就会头疼了;首先就是一些基础概念:并发,并行,同步,异步,临界区,阻塞,非阻塞还有各种锁全都砸你脸上,随之而来的就是要保证程序运行时关键数据在多线程…

最新 Blender 4.2 保姆级安装教程(附安装包)

目录 Blender介绍: Blender下载: Blender改进功能: Blender介绍: Blender是一款开源的跨平台全能三维动画制作软件,提供从建模、渲染、动画、特效、合成到音频处理、视频剪辑等一系列动画短片制作解决方案。它支持…

web安全之信息收集

在信息收集中,最主要是就是收集服务器的配置信息和网站的敏感信息,其中包括域名及子域名信息,目标网站系统,CMS指纹,目标网站真实IP,开放端口等。换句话说,只要是与目标网站相关的信息,我们都应该去尽量搜集。 1.1收集域名信息 知道目标的域名之后,获取域名的注册信…

网络原理(一)—— http

什么是 http http 是一个应用层协议,全称为“超文本传输协议”。 http 自 1991 年诞生,目前已经发展为最主流使用的一种应用层协议。 HTTP 往往基于传输层的 TCP 协议实现的,例如 http1.0,http1.0,http2.0 http3 是…

第四十二篇 EfficientNet:重新思考卷积神经网络的模型缩放

文章目录 摘要1、简介2、相关工作3、复合模型缩放3.1、 问题公式化3.2、扩展维度3.3、复合比例 4、EfficientNet架构5、实验5.1、扩展MobileNets和ResNets5.2、EfficientNet的ImageNet结果5.3、EfficientNet的迁移学习结果 6、讨论7、结论 摘要 卷积神经网络(ConvNets)通常在固…

典型组合逻辑电路设计

目录 行为级描述方式基本运算电路 一、半加器(Half Adder) 二、全加器(Full Adder) 1、逻辑门构成加法器 2、集成全加器 3、串行加法器 4、超前进位加法器 三、全减器(Full Deductor) 数值比较电路 一、一位比较器 二、…

【论文阅读】三平面相关与变体

文章目录 1. 【CVPR2023】Tri-Perspective View for Vision-Based 3D Semantic Occupancy Prediction动机可视化方法Pipeline 2. 【2023/08/31】PointOcc: Cylindrical Tri-Perspective View for Point-based 3D Semantic Occupancy Prediction动机(针对雷达点云、与…

修改bag的frame_id的工具srv_tools

在使用数据集导航或者建图时,bag中的点云或者其他话题的frame_id没有和需要的对应 1.创建工作空间 2.cd xxxx/src 3.git clone https://github.com/srv/srv_tools.git cd .. catkin_make source ./devel/setup.bash rosrun bag_tools change_frame_id.py -t /要改…

hue 4.11容器化部署,已结合Hive与Hadoop

配合《Hue 部署过程中的报错处理》食用更佳 官方配置说明页面: https://docs.gethue.com/administrator/configuration/connectors/ 官方配置hue.ini页面 https://github.com/cloudera/hue/blob/master/desktop/conf.dist/hue.ini docker部署 注意: …

如何用Excel做数据可视化自动化报表?

作为一个经常需要做数据报表的人,我最常用的工具是Excel,对于我来说用Excel处理繁琐冗杂的数据并不难,但是我发现身边很多人用Excel做的数据报表非常的耗时,而且最后的成品也是难以直视,逻辑和配色等都非常的“灾难”。…

layui table 纵向滚动条导致单元格表头表体错位问题

我用的时layui2.6.8版本 历史项目维护,bug给我让我做了,本来利用前端手段强解决,后来发现很多table 找了解决办法 打开layui-v2.6.8/lay/modules/table.js 如果打开后时压缩的代码 直接搜索 e.find(".layui-table-patch") …

C语言学习笔记:流程控制和数据输入输出

流程控制和数据的输入输出 算法 著名计算机科学家沃思提出了一个公式: 数据结构 算法 程序 数据结构:对数据的描述 算法:对操作步骤的描述 算法定义 广义的说,为解决一个问题而采取的方法和有限的步骤,就称为“…

旋转图像(java)

题目描述: 给定一个 n n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。 你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。 代码思路: class Solution {public void ro…

windows 应用 UI 自动化实战

UI 自动化技术架构选型 UI 自动化是软件测试过程中的重要一环,网络上也有很多 UI 自动化相关的知识或资料,具体到 windows 端的 UI 自动化,我们需要从以下几个方面考虑: 开发语言 毋庸置疑,在 UI 自动化测试领域&am…

【R语言管理】Pycharm配置R语言及使用Anaconda管理R语言虚拟环境

目录 使用Anaconda创建R语言虚拟环境1. 安装Anaconda2. 创建R语言虚拟环境 Pycharm配置R语言1. 安装Pycharm2. R Language for IntelliJ插件 参考 使用Anaconda创建R语言虚拟环境 1. 安装Anaconda Anaconda的安装可参见另一博客-【Python环境管理工具】Anaconda安装及使用教程…

C语言进程编程

getpid函数&#xff1a; 原型&#xff1a;pid_t getpid(void) 特性&#xff1a;返回值是PID值 用途&#xff1a;获取当前进程PID 用法例 #include<stdio.h> #include <sys/types.h> #include<unistd.h> int main() {pid_t pid;pid getpid();printf(&qu…

SpringMVC |(一)SpringMVC概述

文章目录 &#x1f4da;SpringMVC概述&#x1f407;三层架构&#x1f407;异步调用 &#x1f4da;SpringMVC入门案例&#x1f407;入门案例&#x1f407;注意事项 &#x1f4da;小结 学习来源&#xff1a;黑马程序员SSM框架教程_SpringSpringMVCMaven高级SpringBootMyBatisPlus…