JIT逆优化引发的Java服务瞬时抖动 问题排查解决方案

news2024/11/17 0:20:41

目录

一、背景

二、前期排查(失败)

三、使用神器JFR

四、学习JIT&思考解决方案

五、最终的解决方案

五、总结

一、背景

我们有一个QPS较高、机器数较多的Java服务;该服务的TP9999一般为几十ms,但偶尔会突然飙升至数秒,并会在几秒内自动恢复(抖动期间伴随着CPU占用100%、线程池大量扩容)。抖动大都集中在新代码上线后的前几天,会随着时间拉长逐渐减少。

二、前期排查(失败)

前期未排查到问题根因,也不知道如何去定位根因;只好从现象出发(CPU占用100% 和 线程池大量扩容),尝试通过解决表面现象,从而避免服务抖动。具体做了以下工作进行测试验证:

工作项预期结果
固定线程池线程数避免因线程创建销毁、线程上下文切换产生的CPU开销抖动时的TP峰值降低,但抖动仍存在
监控线程CPU占用的shell脚本捕获异常时刻到CPU占用高的线程捕获到的线程比较多,有业务代码线程、C2编译器线程、GC线程...
JIT调优(提高编译阈值、减少C2线程数...)降低CPU占用效果不明显
试用JDK21的虚拟线程避免因线程创建销毁、线程上下文切换产生的CPU开销使用虚拟线程后,抖动时的TP峰值降低,但抖动仍存在
试用JDK21的结构化并发避免部分业务线程查存储失败后,其他线程还在运行、持续占用CPU结构化并发也是基于虚拟线程的,效果和虚拟线程类似
试用分代ZGC降低GC线程的CPU占用无效

这时候我们发现针对表面的现象可以做的猜想实在是太多了,对应的实验也太多了,很多时候也很难通过实验去完全地证伪这些猜想。

基于“抖动大都集中在新代码上线后的前几天”的现象,服务冷启动和JIT编译确实有很大的嫌疑,但是JIT编译真的会持续这么多天吗?我们并不能理解,开启了JIT编译日志打印也没看出什么。并且JIT参数调优我们也试了,效果也并不明显。

排查陷入了停滞...

三、使用神器JFR

1、JFR的简介与作用

JFR全程是Java Flight Recorder,即Java飞行记录器。借助JFR我们可以把Java服务的各种事件记录下来,如:各种JIT事件的发生时刻、原因等细节;新开线程的时间;各个时间点各线程对CPU的占用情况...这样就可以把服务异常时刻的各种指标记录下来,大大提升服务的可观测性。

详细了解推荐这位大佬的系列博客:Java 监控 JFR

2、JFR常用命令

# JVM参数开启JFR
-XX:StartFlightRecording=filename=/logs/flight.jfr,maxsize=10g
-XX:FlightRecorderOptions=repository=/logs/tmp #指定临时记录的目录
# 检查正在运行的JFR
jcmd JFR.check
# JFR不会自动导出记录,需要通过命令转储
# 转储所有的记录
jcmd <pid> JFR.dump filename=/logs/flight.jfr
# 转储最后n小时的记录
jcmd <pid> JFR.dump begin=-1h
jcmd <pid> JFR.dump maxage=1h
# 转储指定日期
jcmd <pid> JFR.dump begin=2024-01-01T13:00:00 end=2024-01-01T14:00:00 filename=/logs/flight.jfr

3、使用JFR定位问题根因

有了工具的加持,后面的问题排查就顺利了很多。我们很容易就发现了服务的抖动总是伴随着JIT的逆优化、再编译事件,并且逆优化的原因几乎都是C2激进的分支预测发生了失败,逆优化的代码集中在依赖的json库上。

四、学习JIT&思考解决方案

相关资料

JIT分层编译阈值策略

基本功 | Java即时编译器原理解析及实践 - 美团技术团队

(下图来自上文)

思考

  • 由上述资料我们可以得知,JIT的level 4编译发生逆优化后,代码将发生解释运行
  • 此时我们几乎可以猜测抖动就是来自于JIT逆优化后的解释运行(解释运行性能极差),所以解决方案的核心在于避免逆优化
  • level 1编译不会发生逆优化,可以将分层编译固定在level 1,但是性能会比level 4差30%(实测性能发生了不小的下降,方案不够完美,但TP抖动确实消失了)
  • 因为逆优化集中在json库,尝试更换其他json库(失败,没有效果)
  • 修改分层编译的阈值,避免大量方法被level 2、3、4编译(失败,产生了连锁反应,抖动加剧)
  • 再次陷入了僵局...

五、最终的解决方案

山重水复疑无路,柳暗花明又一村。灵光乍现+好运加成,终于被我找到了两个很有效的方案!

1、使用graal编译器

-XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompile

压测的效果不错,压测了10小时抖动只发生的1~2次,差不多是原来的1/10。

猜测可能是graal对分支预测相关的逻辑有优化,避免了频繁的逆优化及代码的解释运行。

2、修改OpenJDK源码禁用C2的分支预测

  • openjdk编译流程:Building the JDK

  • openjdk源码下载:GitHub - openjdk/jdk: JDK main-line development https://openjdk.org/projects/jdk

  • openjdk源码修改,注释分支预测逻辑,直接返回PROB_FAIR(Fair probability 50/50,即各有一半的机会):

    //-----------------------------branch_prediction-------------------------------
    float Parse::branch_prediction(float& cnt,
                                   BoolTest::mask btest,
                                   int target_bci,
                                   Node* test) {
      return PROB_FAIR;
      // float prob = dynamic_branch_prediction(cnt, btest, test);
      // // If prob is unknown, switch to static prediction
      // if (prob != PROB_UNKNOWN)  return prob;
    
      // prob = PROB_FAIR;                   // Set default value
      // if (btest == BoolTest::eq)          // Exactly equal test?
      //   prob = PROB_STATIC_INFREQUENT;    // Assume its relatively infrequent
      // else if (btest == BoolTest::ne)
      //   prob = PROB_STATIC_FREQUENT;      // Assume its relatively frequent
    
      // // If this is a conditional test guarding a backwards branch,
      // // assume its a loop-back edge.  Make it a likely taken branch.
      // if (target_bci < bci()) {
      //   if (is_osr_parse()) {    // Could be a hot OSR'd loop; force deopt
      //     // Since it's an OSR, we probably have profile data, but since
      //     // branch_prediction returned PROB_UNKNOWN, the counts are too small.
      //     // Let's make a special check here for completely zero counts.
      //     ciMethodData* methodData = method()->method_data();
      //     if (!methodData->is_empty()) {
      //       ciProfileData* data = methodData->bci_to_data(bci());
      //       // Only stop for truly zero counts, which mean an unknown part
      //       // of the OSR-ed method, and we want to deopt to gather more stats.
      //       // If you have ANY counts, then this loop is simply 'cold' relative
      //       // to the OSR loop.
      //       if (data == nullptr ||
      //           (data->as_BranchData()->taken() +  data->as_BranchData()->not_taken() == 0)) {
      //         // This is the only way to return PROB_UNKNOWN:
      //         return PROB_UNKNOWN;
      //       }
      //     }
      //   }
      //   prob = PROB_STATIC_FREQUENT;     // Likely to take backwards branch
      // }
    
      // assert(prob != PROB_UNKNOWN, "must have some guess at this point");
      // return prob;
    }

    压测的效果极好,抖动几乎完全消失,并且接口的AVG、TP9999指标并未发生明显下降。

六、总结

  1. 可观测性对计算机系统极其重要,良好的可观测性可以大大提高问题排查、性能优化的效率
  2. 工欲善其事,必先利其器。掌握各种性能分析、问题排查、效率提升工具的使用是很有必要的
  3. 先分析清楚问题的根因才可以解决问题,没找到正确方向的努力只会是隔靴搔痒
  4. 阅读第一手的文档资料(当然大都是英文的),才能得到最准确的信息(这里推荐一个浏览器插件“沉浸式翻译”,可以实现中文与原文的对照阅读)
  5. 对于不同的技术积累,解决问题的维度也是不一样的。熟悉底层技术/源码,能做出惊艳的效果。

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

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

相关文章

图片太大如何缩小上传?分享一个解决办法

随着手机、相机和其他设备拍摄照片的分辨率和质量不断提高&#xff0c;图片大小也越来越大&#xff0c;在设备存储空间有限的情况下&#xff0c;通过压缩图片可以节省存储空间&#xff0c;使得能够存储更多的图片文件&#xff0c;怎么把图片压缩小一点呢&#xff1f; 想要将图片…

Linux嵌入式开发+驱动开发-中断

swi汇编指令可以产生软中断&#xff0c;以下是硬件中断的产生到执行完毕的全过程&#xff1a; 在自己设计的芯片“CPU响应中断”程序的第四个步骤可以转向“中断向量控制器”&#xff0c;中断向量控制器中存储中断元服务地址即处理中断处理程序的地址&#xff0c;而不用使用0X1…

阅读笔记——《RapidFuzz: Accelerating fuzzing via Generative Adversarial Networks》

【参考文献】Ye A, Wang L, Zhao L, et al. Rapidfuzz: Accelerating fuzzing via generative adversarial networks[J]. Neurocomputing, 2021, 460: 195-204.【注】本文仅为作者个人学习笔记&#xff0c;如有冒犯&#xff0c;请联系作者删除。 目录 摘要 一、介绍 二、相关…

java面试题:MySQL中的各种JOIN的区别

表关联是频率非常高的一种数据库操作&#xff0c;在MySQL中&#xff0c;这种JOIN操作有很多类型&#xff0c;包括内联接、左外连接、右外连接等等&#xff0c;而每种连接的含义都不一样&#xff0c;如果死记硬背&#xff0c;不仅很难记住&#xff0c;而且也容易搞混淆&#xff…

简单的TcpServer(英译中)

目录 一、TCP socket API 详解1.1 socket()1.2 bind()1.3 listen()1.4 accept()1.5 connect 二、TcpServer&#xff08;英译中&#xff09;2.1 TcpServer.hpp2.2 TcpClient.cc2.3 Task.hpp2.4 Thread.hpp2.5 ThreadPool.hpp2.6 makefile2.7 Main.cc2.8 log.hpp2.9 Init.hpp2.10…

23、数据结构/查找相关练习20240205

一、请编程实现哈希表的创建存储数组{12,24,234,234,23,234,23},输入key查找的值&#xff0c;实现查找功能。 代码&#xff1a; #include<stdlib.h> #include<string.h> #include<stdio.h> #include<math.h> typedef struct Node {int data;struct n…

1.0 Zookeeper 分布式配置服务教程

ZooKeeper 是 Apache 软件基金会的一个软件项目&#xff0c;它为大型分布式计算提供开源的分布式配置服务、同步服务和命名注册。 ZooKeeper 的架构通过冗余服务实现高可用性。 Zookeeper 的设计目标是将那些复杂且容易出错的分布式一致性服务封装起来&#xff0c;构成一个高…

Leetcode24:两两交换链表中的节点

一、题目 给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题&#xff08;即&#xff0c;只能进行节点交换&#xff09;。 示例&#xff1a; 输入&#xff1a;head [1,2,3,4] 输出&#xff…

JVM 性能调优- Java 中的五种内存溢出(5)

在介绍之前先简单介绍下 直接内存(Direct Memory)和堆内存(Heap Memory): 关系: 直接内存并不是Java虚拟机的一部分,它是通过Java的NIO库中的ByteBuffer来分配和管理的。直接内存通常由操作系统的本地内存(Native Memory)提供支持。堆内存是Java虚拟机的一部分,用于存…

杨中科 ASP.NETCORE 高级14 SignalR

1、什么是websocket、SignalR 服务器向客户端发送数据 1、需求&#xff1a;Web聊天;站内沟通。 2、传统HTTP&#xff1a;只能客户端主动发送请求 3、传统方案&#xff1a;长轮询&#xff08;Long Polling&#xff09;。缺点是&#xff1f;&#xff08;1.客户端发送请求后&…

单片机的了解与主流型号有哪些?

单片机是一种集成电路芯片&#xff0c;采用超大规模集成电路技术将具有数据处理能力的中央处理器 CPU、随机存储器 RAM、只读存储器 ROM、多种 I/O 口和中断系统、定时器/计数器等功能集成到一块硅片上&#xff0c;构成一个微型计算机系统。单片机广泛应用于工业控制领域&#…

四、树立边界(Negotiating Boundaries)

2.Negotiating Boundaries 二、协商边界 Other people are the biggest obstacles of focus.A colleague wants to chat.You get a WeChat message about that party tonight.You hear the familiar ping of new emails that demand a look. 他人是保持专注的最大障碍。同事想要…

Java TreeSet 添加自定义对象 必须指定排序规则

Java TreeSet 添加自定义对象 必须指定排序规则 package com.zhong.collection.set;import java.util.Comparator; import java.util.TreeSet;public class TreeSetDemo {public static void main(String[] args) {// TreeSet 添加自定义数据类型 应该自定义排序规则TreeSet<…

openssl3.2 - exp - buffer to BIO

文章目录 openssl3.2 - exp - buffer to BIO概述笔记END openssl3.2 - exp - buffer to BIO 概述 openssl的资料看的差不多了, 准备将工程中用到的知识点整理一下. openssl中很多API是以操作文件作为输入的, 也有很多API是以BIO作为输入的. 不管文件是不是受保护的, 如果有可…

游戏服务器租赁多少钱一台?26元,服不服?

游戏服务器租用多少钱一年&#xff1f;1个月游戏服务器费用多少&#xff1f;阿里云游戏服务器26元1个月、腾讯云游戏服务器32元&#xff0c;游戏服务器配置从4核16G、4核32G、8核32G、16核64G等配置可选&#xff0c;可以选择轻量应用服务器和云服务器&#xff0c;阿腾云atengyu…

SpringCloud-搭建Nacos服务中心

Nacos 是一个开源的动态服务发现、配置管理和服务管理平台。它支持多种服务发现协议&#xff0c;包括基于 DNS 和 HTTP 的服务发现。Nacos 提供了强大的配置管理和服务发现功能&#xff0c;使得在微服务架构中轻松实现服务注册、发现和配置管理成为可能。在本篇博客中&#xff…

微信小程序(三十六)事件传参

注释很详细&#xff0c;直接上代码 上一篇 新增内容&#xff1a; 1.传参步骤 2.传参接收解构步骤 源码&#xff1a; index.wxml <button type"primary" bind:tap"onclick" mark:index"{{0}}" mark:remb"{{1}}" class"But&quo…

【Axure高保真原型】计算日期区间的天数差

今天和大家分享计算日期区间的天数差的原型模板&#xff0c;选择开始日期和结束日期&#xff0c;点击等于按钮后&#xff0c;就可以计算出这两个日期之间相差了多少天&#xff0c;本案例提供中继器版的日期选择器&#xff0c;以及JS版的日期选择器&#xff0c;具体效果可以观看…

kafka-splunk数据通路实践

目的&#xff1a; 鉴于目前网络上没有完整的kafka数据投递至splunk教程&#xff0c;通过本文操作步骤&#xff0c;您将实现kafka数据投递至splunk日志系统 实现思路&#xff1a; 创建kafka集群部署splunk&#xff0c;设置HTTP事件收集器部署connector服务创建connector任务&a…

【工具】Android|Android Studio 长颈鹿版本安装下载使用详解

版本&#xff1a;2022.3.1.22&#xff0c; https://redirector.gvt1.com/edgedl/android/studio/install/2022.3.1.22/android-studio-2022.3.1.22-windows.exe 前言 笔者曾多次安装并卸载Android Studio&#xff0c;反复被安卓模拟器劝退。现在差不多是第三次安装&#xff0c…