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容器。堆内存配置xms是1g,xmx是2.5g。

3.2,解决方案 

ZGC的总体逻辑是这样的,它其实并非降内存消耗,只是降停顿,所以其实需要额外的内存空间来完成这件事。 JVM 总内存 ≈ 堆内存(Xmx) + 堆外内存 + 元空间 + 线程栈数量 * Xss + 额外内存;这个额外内存是需要留下来的。按照你的xmx=1.5,额外内存基本上最好得在0.8+(50%以上)。再就是,xms!=xmx 这个设置是出现毛刺的原因之一。一般会设置一样或者差不多。

从Java8到升级到17/21,我们之前有个过程。首先8到11,把gc从ps换成g1,这一步因为是有预期的,g1最好给大内存,所以都是8g的。然后11再到17/21,内存是适配的。一步从8到17/21的话,我建议考虑这样做:

  • 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出空间导致一下冲破最大限制。

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

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

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

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

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

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

相关文章

屏幕分辨率|尺寸|颜色深度指纹修改

一、前端通过window.screen接口获取屏幕分辨率 尺寸 颜色深度,横屏竖屏信息。 二、window.screen c接口实现: 1、third_party\blink\renderer\core\frame\screen.idl // https://drafts.csswg.org/cssom-view/#the-screen-interface[ExposedWindow ] …

【C#设计模式(15)——命令模式(Command Pattern)】

前言 命令模式的关键通过将请求封装成一个对象,使命令的发送者和接收者解耦。这种方式能更方便地添加新的命令,如执行命令的排队、延迟、撤销和重做等操作。 代码 #region 基础的命令模式 //命令(抽象类) public abstract class …

Linux基础项目包含(DNS,FTP,Samba)

今天我们来实现一个linux的基础项目,多做项目可以加深我们对每个服务的掌握程度,能更加的熟悉各个服务的使用,以及每个服务之间如何相互应用,我们可以有一个清晰的认知,那么接下来我们就开始项目的实操。 一、 项目背…

鸿蒙面试 --- 性能优化

性能优化可以从三个方面入手 感知流畅、渲染性能、运行性能 感知流畅 在应用开发中,动画可以为用户界面增添生动、流畅的交互效果,提升用户对应用的好感度。然而,滥用动画也会导致应用性能下降,消耗过多的系统资源,…

UWB数字钥匙安全测距和场景应用

1. CCC数字钥匙 2021年7月CCC将UWB定义为第三代数字钥匙的核心技术,并发布CCC R3(第三代数字钥匙)规范。 CCC R3是基于NFC/BLE/UWB作为基础的无线电技术的使用,该系统采用非对称密码技术对车辆和设备进行相互签名认证&#xff0…

使用Compose Multiplatform开发跨平台的Android调试工具

背景 最近对CMP跨平台很感兴趣,为了练手,在移动端做了一个Android和IOS共享UI和逻辑代码的天气软件,简单适配了一下双端的深浅主题切换,网络状态监测,刷新调用振动器接口。 做了两年多车机Android开发,偶…

ubuntu22开机自动登陆和开机自动运行google浏览器自动打开网页

一、开机自动登陆 1、打开settings->点击Users 重启系统即可自动登陆桌面 二、开机自动运行google浏览器自动打开网页 1、安装google浏览器 sudo wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb sudo dpkg -i ./google-chrome-stable…

前端学习笔记之文件下载(1.0)

因为要用到这样一个场景,需要下载系统的使用教程,所以在前端项目中就提供了一个能够下载系统教程的一个按钮,供使用者进行下载。 所以就试着写一下这个功能,以一个demo的形式进行演示,在学习的过程中也发现了中文路径…

FPGA实现GTP光口视频转USB3.0传输,基于FT601+Aurora 8b/10b编解码架构,提供3套工程源码和技术支持

目录 1、前言工程概述免责声明 2、相关方案推荐我已有的所有工程源码总目录----方便你快速找到自己喜欢的项目我这里已有的 GT 高速接口解决方案本博已有的FPGA驱动USB通信方案 3、工程详细设计方案工程设计原理框图输入Sensor之-->OV5640摄像头输入Sensor之-->芯片解码的…

借助nntools的模型训练(图像去噪任务, PyTorch)

在跑通开源代码的过程中,一直难以找到很合适的代码框架或代码模板,尤其对于初学者而言是不小的挑战。本人在跑代码的过程中,发现在GitHub上有一个辅助训练过程的模板。这篇文章主要记录它的使用方法,并标记来源。 代码均在jupyter…

C# 常量

文章目录 前言一、整数常量(一)合法与非法实例对比(二)不同进制及类型示例 二、浮点常量三、字符常量四、字符串常量五、定义常量 前言 在 C# 编程的世界里,常量是一类特殊的数据元素,它们如同程序中的 “定…

PHP 生成分享海报

因为用户端有多个平台,如果做分享海报生成,需要三端都来做,工作量比较大。 所以这个艰巨的任务就光荣的交给后端了。经过一定时间的研究和调试,最终圆满完成了任务,生成分享海报图片实现笔记如下。 目录 准备字体文件…

探索Python词云库WordCloud的奥秘

文章目录 探索Python词云库WordCloud的奥秘1. 背景介绍:为何选择WordCloud?2. WordCloud库简介3. 安装WordCloud库4. 简单函数使用方法5. 应用场景示例6. 常见Bug及解决方案7. 总结 探索Python词云库WordCloud的奥秘 1. 背景介绍:为何选择Wo…

git 学习笔记

目录 一、git 前期配置准备 1、托管平台的账号注册(以gitee码云为demo) 2、本地个人电脑配置 (1)配置用户属性 ​编辑 (2)配置SSH密钥 二、git 工作流程图 三、git 提交命令 (1&#x…

uniapp中使用uni-forms实现表单管理,验证表单

前言 uni-forms 是一个用于表单管理的组件。它提供了一种简化和统一的方式来处理表单数据,包括表单验证、字段绑定和提交逻辑等。使用 uni-forms可以方便地创建各种类型的表单,支持数据双向绑定,可以与其他组件及API进行良好的集成。开发者可…

_C#_串口助手_字符串拼接缺失问题(未知原理)

最近使用WPF开发串口助手时,遇到一个很奇怪的问题,无论是主线程、异步还是多线程,当串口接收速度达到0.016s一次以上,就会发生字符串缺失问题并且很卡。而0.016s就一切如常,仿佛0.015s与0.016s是天堑之隔。 同一份代码…

JVM(JAVA虚拟机)内存溢出导致内存不足,Java运行时环境无法继续

1、先贴出服务最后打印出来的日志,意思就是给虚拟机分配的内存被用完了,没有可用的内存了,服务运行不了了,被动停服了。详细的日志记录在了/home/user/zx/tomcat/apache-tomcat-8.5.82/bin/hs_err_pid147951.log文件里。 Java Ho…

OGRE 3D----3. OGRE绘制自定义模型

在使用OGRE进行开发时,绘制自定义模型是一个常见的需求。本文将介绍如何使用OGRE的ManualObject类来创建和绘制自定义模型。通过ManualObject,开发者可以直接定义顶点、法线、纹理坐标等,从而灵活地构建各种复杂的几何体。 Ogre::ManualObject 是 Ogre3D 引擎中的一个类,用…

Spring Boot英语知识网站:架构与开发

4系统概要设计 4.1概述 本系统采用B/S结构(Browser/Server,浏览器/服务器结构)和基于Web服务两种模式,是一个适用于Internet环境下的模型结构。只要用户能连上Internet,便可以在任何时间、任何地点使用。系统工作原理图如图4-1所示: 图4-1系统工作原理…

如何借助AI生成PPT,让创作轻松又高效

PPT是现代职场中不可或缺的表达工具,但同时也可能是令人抓狂的时间杀手。几页幻灯片的制作,常常需要花费数小时调整字体、配色与排版。AI的飞速发展为我们带来了革新——AI生成PPT的技术不仅让制作流程大大简化,还重新定义了效率与创意的关系…