系统突然运行慢,线上CPU100%,频繁FullGC排查

2023-10-31

当然,这些问题最终导致的直观现象就是系统运行缓慢,并且有大量的报警。

本文主要针对系统运行缓慢这一问题,提供该问题的排查思路,从而定位出问题的代码点,进而提供解决该问题的思路。

对于线上系统突然产生的运行缓慢问题,如果该问题导致线上系统不可用,那么首先需要做的就是,导出 jstack 和内存信息,然后重启系统,尽快保证系统的可用性。

这种情况可能的原因主要有两种:

  • 代码中某个位置读取数据量较大,导致系统内存耗尽,从而导致 Full GC 次数过多,系统缓慢。

  • 代码中有比较耗 CPU 的操作,导致 CPU 过高,系统运行缓慢。

相对来说,这是出现频率最高的两种线上问题,而且它们会直接导致系统不可用。

另外有几种情况也会导致某个功能运行缓慢,但是不至于导致系统不可用:

        代码某个位置有阻塞性的操作,导致该功能调用整体比较耗时,但出现是比较随机的。

  • 某个线程由于某种原因而进入 WAITING 状态,此时该功能整体不可用,但是无法复现。

  • 由于锁使用不当,导致多个线程进入死锁状态,从而导致系统整体比较缓慢。

对于这三种情况,通过查看 CPU 和系统内存情况是无法查看出具体问题的,因为它们相对来说都是具有一定阻塞性操作,CPU 和系统内存使用情况都不高,但是功能却很慢。

下面我们就通过查看系统日志来一步一步甄别上述几种问题。

Full GC 次数过多

相对来说,这种情况是最容易出现的,尤其是新功能上线时。

对于 Full GC 较多的情况,其主要有如下两个特征:

  • 线上多个线程的 CPU 都超过了 100%,通过 jstack 命令可以看到这些线程主要是垃圾回收线程。

  • 通过 jstat 命令监控 GC 情况,可以看到 Full GC 次数非常多,并且次数在不断增加。

首先我们可以使用 top 命令查看系统 CPU 的占用情况,如下是系统 CPU 较高的一个示例:

可以看到,有一个 Java 程序此时 CPU 占用量达到了 98.8%,此时我们可以复制该进程 id9,并且使用如下命令查看该进程的各个线程运行情况:

该进程下的各个线程运行情况如下:

可以看到,在进程为 9 的 Java 程序中各个线程的 CPU 占用情况,接下来我们可以通过 jstack 命令查看线程 id 为 10 的线程为什么耗费 CPU 最高。

 

需要注意的是,在 jsatck 命令展示的结果中,线程 id 都转换成了十六进制形式。

 

可以用如下命令查看转换结果,也可以找一个科学计算器进行转换:

这里打印结果说明该线程在 jstack 中的展现形式为 0xa,通过 jstack 命令我们可以看到如下信息:

这里的 VM Thread 一行的最后显示 nid=0xa,这里 nid 的意思就是操作系统线程 id 的意思,而 VM Thread 指的就是垃圾回收的线程。

 

这里我们基本上可以确定,当前系统缓慢的原因主要是垃圾回收过于频繁,导致 GC 停顿时间较长。

 

我们通过如下命令可以查看 GC 的情况:

可以看到,这里 FGC 指的是 Full GC 数量,这里高达 6793,而且还在不断增长。从而进一步证实了是由于内存溢出导致的系统缓慢。

 

那么这里确认了内存溢出,但是如何查看你是哪些对象导致的内存溢出呢,这个可以 Dump 出内存日志,然后通过 Eclipse 的 Mat 工具进行查看。

 

如下图是其展示的一个对象树结构:

 

经过 Mat 工具分析之后,我们基本上就能确定内存中主要是哪个对象比较消耗内存,然后找到该对象的创建位置,进行处理即可。

 

这里主要是 PrintStream 最多,但是我们也可以看到,其内存消耗量只有 12.2%。

 

也就是说,其还不足以导致大量的 Full GC,此时我们需要考虑另外一种情况,就是代码或者第三方依赖的包中有显示的 System.gc() 调用。

 

这种情况我们查看 Dump 内存得到的文件即可判断,因为其会打印 GC 原因:

比如这里第一次 GC 是由于 System.gc() 的显示调用导致的,而第二次 GC 则是 JVM 主动发起的。

 

总结来说,对于 Full GC 次数过多,主要有以下两种原因:

  • 代码中一次获取了大量的对象,导致内存溢出,此时可以通过 Eclipse 的 Mat 工具查看内存中有哪些对象比较多。

  • 内存占用不高,但是 Full GC 次数还是比较多,此时可能是显示的 System.gc() 调用导致 GC 次数过多,这可以通过添加 -XX:+DisableExplicitGC 来禁用 JVM 对显示 GC 的响应。

 

CPU 过高

 

在前面第一点中,我们讲到,CPU 过高可能是系统频繁的进行 Full GC,导致系统缓慢。

 

而我们平常也肯定能遇到比较耗时的计算,导致 CPU 过高的情况,此时查看方式其实与上面的非常类似。

 

首先我们通过 top 命令查看当前 CPU 消耗过高的进程是哪个,从而得到进程 id;然后通过 top -Hp <pid> 来查看该进程中有哪些线程 CPU 过高,一般超过 80% 就是比较高的,80% 左右是合理情况。

 

这样我们就能得到 CPU 消耗比较高的线程 id。接着通过该线程 id 的十六进制表示在 jstack 日志中查看当前线程具体的堆栈信息。

 

在这里我们就可以区分导致 CPU 过高的原因具体是 Full GC 次数过多还是代码中有比较耗时的计算了。

 

如果是 Full GC 次数过多,那么通过 jstack 得到的线程信息会是类似于 VM Thread 之类的线程。

 

而如果是代码中有比较耗时的计算,那么我们得到的就是一个线程的具体堆栈信息。

 

如下是一个代码中有比较耗时的计算,导致 CPU 过高的线程信息:

 

这里可以看到,在请求 UserController 的时候,由于该 Controller 进行了一个比较耗时的调用,导致该线程的 CPU 一直处于 100%。

 

我们可以根据堆栈信息,直接定位到 UserController 的 34 行,查看代码中具体是什么原因导致计算量如此之高。

 

不定期出现的接口耗时现象

 

对于这种情况,比较典型的例子就是,我们某个接口访问经常需要 2~3s 才能返回。

 

这是比较麻烦的一种情况,因为一般来说,其消耗的 CPU 不多,而且占用的内存也不高,也就是说,我们通过上述两种方式进行排查是无法解决这种问题的。

 

而且由于这样的接口耗时比较大的问题是不定时出现的,这就导致了我们在通过 jstack 命令即使得到了线程访问的堆栈信息,我们也没法判断具体哪个线程是正在执行比较耗时操作的线程。

 

对于不定时出现的接口耗时比较严重的问题,我们的定位思路基本如下:首先找到该接口,通过压测工具不断加大访问力度。

 

如果说该接口中有某个位置是比较耗时的,由于我们的访问的频率非常高,那么大多数的线程最终都将阻塞于该阻塞点。

 

这样通过多个线程具有相同的堆栈日志,我们基本上就可以定位到该接口中比较耗时的代码的位置。

 

如下是一个代码中有比较耗时的阻塞操作通过压测工具得到的线程堆栈日志:

从上面的日志你可以看出,这里有多个线程都阻塞在了 UserController 的第 18 行,说明这是一个阻塞点,也就是导致该接口比较缓慢的原因。

 

某个线程进入 WAITING 状态

 

对于这种情况,这是比较罕见的一种情况,但是也是有可能出现的,而且由于其具有一定的“不可复现性”,因而我们在排查的时候是非常难以发现的。

 

笔者曾经就遇到过类似的这种情况,具体的场景是,在使用 CountDownLatch 时,由于需要每一个并行的任务都执行完成之后才会唤醒主线程往下执行。

 

而当时我们是通过 CountDownLatch 控制多个线程连接并导出用户的 Gmail 邮箱数据,这其中有一个线程连接上了用户邮箱,但是连接被服务器挂起了,导致该线程一直在等待服务器的响应。

 

最终导致我们的主线程和其余几个线程都处于 WAITING 状态。

 

对于这样的问题,查看过 jstack 日志的读者应该都知道,正常情况下,线上大多数线程都是处于 TIMED_WAITING 状态。

 

而我们这里出问题的线程所处的状态与其是一模一样的,这就非常容易混淆我们的判断。

 

解决这个问题的思路主要如下:

 

①通过 grep 在 jstack 日志中找出所有的处于 TIMED_WAITING 状态的线程,将其导出到某个文件中,如 a1.log,如下是一个导出的日志文件示例:

②等待一段时间之后,比如 10s,再次对  jstack 日志进行 grep,将其导出到另一个文件,如 a2.log,结果如下所示:

③重复步骤 2,待导出 3~4 个文件之后,我们对导出的文件进行对比,找出其中在这几个文件中一直都存在的用户线程。

 

这个线程基本上就可以确认是包含了处于等待状态有问题的线程。因为正常的请求线程是不会在 20~30s 之后还是处于等待状态的。

 

④经过排查得到这些线程之后,我们可以继续对其堆栈信息进行排查,如果该线程本身就应该处于等待状态,比如用户创建的线程池中处于空闲状态的线程,那么这种线程的堆栈信息中是不会包含用户自定义的类的。

 

这些都可以排除掉,而剩下的线程基本上就可以确认是我们要找的有问题的线程。

 

通过其堆栈信息,我们就可以得出具体是在哪个位置的代码导致该线程处于等待状态了。

 

这里需要说明的是,我们在判断是否为用户线程时,可以通过线程最前面的线程名来判断,因为一般的框架的线程命名都是非常规范的。

 

我们通过线程名就可以直接判断得出该线程是某些框架中的线程,这种线程基本上可以排除掉。

 

而剩余的,比如上面的 Thread-0,以及我们可以辨别的自定义线程名,这些都是我们需要排查的对象。

 

经过上面的方式进行排查之后,我们基本上就可以得出这里的 Thread-0 就是我们要找的线程,通过查看其堆栈信息,我们就可以得到具体是在哪个位置导致其处于等待状态了。

 

如下示例中则是在 SyncTask 的第 8 行导致该线程进入等待了:

死锁

 

对于死锁,这种情况基本上很容易发现,因为 jstack 可以帮助我们检查死锁,并且在日志中打印具体的死锁线程信息。

 

如下是一个产生死锁的一个 jstack 日志示例:

 

可以看到,在 jstack 日志的底部,其直接帮我们分析了日志中存在哪些死锁,以及每个死锁的线程堆栈信息。

 

这里我们有两个用户线程分别在等待对方释放锁,而被阻塞的位置都是在 ConnectTask 的第 5 行,此时我们就可以直接定位到该位置,并且进行代码分析,从而找到产生死锁的原因。

 

小结

本文主要讲解了线上可能出现的五种导致系统缓慢的情况,详细分析了每种情况产生时的现象,已经根据现象我们可以通过哪些方式定位得到是这种原因导致的系统缓慢。

简要的说,我们进行线上日志分析时,主要可以分为如下步骤:

①通过 top 命令查看 CPU 情况,如果 CPU 比较高,则通过 top -Hp <pid> 命令查看当前进程的各个线程运行情况。

找出 CPU 过高的线程之后,将其线程 id 转换为十六进制的表现形式,然后在 jstack 日志中查看该线程主要在进行的工作。

这里又分为两种情况:

  • 如果是正常的用户线程,则通过该线程的堆栈信息查看其具体是在哪处用户代码处运行比较消耗 CPU。

  • 如果该线程是 VM Thread,则通过 jstat -gcutil <pid> <period> <times> 命令监控当前系统的 GC 状况。

    然后通过 jmap dump:format=b,file=<filepath> <pid> 导出系统当前的内存数据。

    导出之后将内存情况放到 Eclipse 的 Mat 工具中进行分析即可得出内存中主要是什么对象比较消耗内存,进而可以处理相关代码。

②如果通过 top 命令看到 CPU 并不高,并且系统内存占用率也比较低。此时就可以考虑是否是由于另外三种情况导致的问题。

具体的可以根据具体情况分析:

  • 如果是接口调用比较耗时,并且是不定时出现,则可以通过压测的方式加大阻塞点出现的频率,从而通过 jstack 查看堆栈信息,找到阻塞点。

  • 如果是某个功能突然出现停滞的状况,这种情况也无法复现,此时可以通过多次导出 jstack 日志的方式对比哪些用户线程是一直都处于等待状态,这些线程就是可能存在问题的线程。

  • 如果通过 jstack 可以查看到死锁状态,则可以检查产生死锁的两个线程的具体阻塞点,从而处理相应的问题。

本文主要是提出了五种常见的导致线上功能缓慢的问题,以及排查思路。当然,线上的问题出现的形式是多种多样的,也不一定局限于这几种情况。

如果我们能够仔细分析这些问题出现的场景,就可以根据具体情况具体分析,从而解决相应的问题。

转摘于:https://mp.weixin.qq.com/s/OO1LljSgUxRJ3Y8A3sfEng

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

系统突然运行慢,线上CPU100%,频繁FullGC排查 的相关文章

  • 清理码头 - 删除“不必要”的东西

    我习惯用Jetty http jetty codehaus org jetty 作为我的网络容器 我对我做了什么安装步骤得到原始的焦油球并且清理一些目录和文件从中 我在这里想提出的是 您通常从 Jetty 中删除什么以在生产 登台环境中使用
  • 将 jar 作为 Linux 服务运行 - init.d 脚本在启动应用程序时卡住

    我目前正在致力于在 Linux VM 上实现一个可运行的 jar 作为后台服务 我已经使用了找到的例子here https gist github com shirish4you 5089019作为工作的基础 并将 start 方法修改为
  • “_加载小部件时出现问题”消息

    加载小部件时 如果找不到资源或其他内容 则会显示 加载小部件时出现问题 就这样 惊人的 此消息保留在主屏幕上 甚至没有说明加载时遇到问题的小部件 我通过反复试验弄清楚了这一点 但我想知道发生这种情况时是否有任何地方可以找到错误消息 Andr
  • 如何使用 SimpleDateFormat 解析多种格式的日期

    我正在尝试解析文档中的一些日期 用户似乎以类似但不完全相同的格式输入了这些日期 以下是格式 9 09 9 2009 09 2009 9 1 2009 9 1 2009 尝试解析所有这些内容的最佳方法是什么 这些似乎是最常见的 但我想让我困扰
  • 对话框上的 EditText 不返回任何文本

    我太累了 找不到错误 我没有发现任何错误 但我没有从 editText 收到任何文本 请看下面的代码 活动密码 xml
  • 如何从秘密字符串中制作 HMAC_SHA256 密钥以在 jose4j 中与 JWT 一起使用?

    我想生成 JWT 并使用 HMAC SHA256 对其进行签名 对于该任务我必须使用jose4j https bitbucket org b c jose4j wiki Home 我尝试根据秘密生成密钥 SecretKeySpec key
  • Firestore - RecycleView - 图像持有者

    我不知道如何编写图像的支架 我已经设置了 2 个文本 但我不知道图像的支架应该是什么样子 你能帮我告诉我图像的文字应该是什么样子才能正确显示吗 holder artistImage setImageResource model getArt
  • Java:正则表达式排除空值

    在问题中here https stackoverflow com questions 51359056 java regexp for a separated group of digits 我得到了正则表达式来匹配 1 到 99 之间的一
  • 如何获取 WebElement 的父级[重复]

    这个问题在这里已经有答案了 我试过了 private WebElement getParent final WebElement webElement return webElement findElement By xpath 但我得到
  • Java 服务器-客户端 readLine() 方法

    我有一个客户端类和一个服务器类 如果客户端向服务器发送消息 服务器会将响应发送回客户端 然后客户端将打印它收到的所有消息 例如 如果客户端向服务器发送 A 则服务器将向客户端发送响应 1111 所以我在客户端类中使用 readLine 从服
  • 将表值参数与 SQL Server JDBC 结合使用

    任何人都可以提供一些有关如何将表值参数 TVP 与 SQL Server JDBC 一起使用的指导吗 我使用的是微软提供的6 0版本的SQL Server驱动程序 我已经查看了官方文档 https msdn microsoft com en
  • Git 无法识别重命名和修改的包文件

    我有一个名为的java文件package old myfile java 我已经通过 git 提交了这个文件 然后我将我的包重命名为new所以我的文件在package new myfile java 我现在想将此文件重命名 和内容更改 提交
  • 如何将 Jfreechart(饼图)添加到 netbeans 的面板中

    我正在使用 netbeans gui 编辑器 并且正在尝试添加一个本身位于内部框架中的 Jfreechart 并且这个内部框架我想将其添加到面板中 正如您在此图中看到的那样 抱歉 我无法直接发布图像 因为我新手 http www flick
  • 计算日期之间的天数差异

    在我的代码中 日期之间的差异是错误的 因为它应该是 38 天而不是 8 天 我该如何修复 package random04diferencadata import java text ParseException import java t
  • Java 收集返回顶级项目的映射的嵌套流

    我有以下模型 class Item String name List
  • Android Studio 将音乐文件读取为文本文件,如何恢复它?

    gameAlert mp3是我的声音文件 运行应用程序时 它询问我该文件不与任何文件类型关联 请定义关联 我选择TextFile错误地 现在我的音乐文件被读取为文本文件 我如何将其转换回music file protected void o
  • Linux 上有关 getBounds() 和 setBounds() 的 bug_id=4806603 的解决方法?

    在 Linux 平台上 Frame getBounds 和 Frame setBounds 的工作方式不一致 这在 2003 年就已经有报道了 请参见此处 http bugs java com bugdatabase view bug do
  • JVM:是否可以操作帧堆栈?

    假设我需要执行N同一线程中的任务 这些任务有时可能需要来自外部存储的一些值 我事先不知道哪个任务可能需要这样的值以及何时 获取速度要快得多M价值观是一次性的而不是相同的M值在M查询外部存储 注意我不能指望任务本身进行合作 它们只不过是 ja
  • 在android中跟踪FTP上传数据?

    我有一个运行 Android 的 FTP 系统 但我希望能够在上传时跟踪字节 这样我就可以在上传过程中更新进度条 安卓可以实现这个功能吗 现在 我正在使用org apache common net ftp我正在使用的代码如下 另外 我在 A
  • Java &= 运算符应用 & 或 && 吗?

    Assuming boolean a false 我想知道是否这样做 a b 相当于 a a b logical AND a is false hence b is not evaluated 或者另一方面 这意味着 a a b Bitwi

随机推荐

  • 以太坊开发框架——Truffle的基础使用

    这里写目录标题 Truffle Truffle 简介 Truffle 的客户端 安装Truffle 创建项目 Migration artifacts require exports 的函数 deployer 对象 更新 migration
  • TCP三次握手

    TCP三次握手的原因 双方都确认对方具有接收和发送数据的功能 1 初始状态 双方都处于Closed状态 2 服务器开启监听功能 处于Listen状态 3 第一次握手 客户端发起请求 发送一个SYN标识 连接请求数据包 seq x 并处于SY
  • vue 树形结构数据的便捷遍历,及树形结构与平级列表的相互转换(使用xe-utils函数)

    一 使用xe utils函数 xe utils 的api地址 xe utils 函数库 工具类 二 安装 npm安装 npm install xe utils 引用 import XEUtils from xe utils 1 mapTre
  • 数据结构(使用静态数组实现顺序表)

    一 定义 1 线性表 1 线性表的定义 逻辑结构 具有相同数据类型的n n gt 0 的有限个数的数据元素的有序排列 2 线性表的运算 操作 创建销毁 增删改查 3 线性表的存储结构 顺序存储 产生了顺序表 链式存储 产生了链表 2 顺序表
  • 华为云服务器怎么传文件,在云服务器终端里面怎么传文件

    在云服务器终端里面怎么传文件 内容精选 换一换 已获取该弹性云服务器的密钥文件 弹性云服务器已经绑定弹性IP地址 已配置安全组入方向的访问规则 如果您是在Windows操作系统上登录Linux 可以按照下面方式登录弹性云服务器 我们以PuT
  • 数据结构——单调栈

    单调栈 定义 单调递增栈 单调递增栈就是从栈底到栈顶数据是从小到大 单调递减栈 单调递减栈就是从栈底到栈顶数据是从大到小 实现 以单调递增栈为例 向栈中推入元素时 如果栈顶元素比当前元素大 则将栈顶元素推出 直到栈顶元素比当前元素小或者栈为
  • IDEA新建项目时,没有Spring Initializr选项(亲测有效)

    最近开始使用IDEA作为开发工具 然后也是打算开始学习使用spring boot 看着博客来进行操作上手spring boot 很多都是说 创建一个新项目 Create New Project 选择 Spring Initializr 然而
  • 全网最全jupyter安装与使用教程

    jupyter的安装与使用 注 我主要使用的是windows系统 其余的也不太了解 不过这篇文章可以解决大部分问题 部分图片过大 建议打开网址 简介 Jupyter Notebook是基于网页的用于交互计算的应用程序 简而言之 Jupyte
  • c++输出字符数组出现汉字乱码(包含用for循环输入字符数组再输出数组,出现汉字乱码)

    原因在于字符数组里存放内容没有字符结束标志 0 例子1 没有 0 include
  • c#翻页效果

    用c 和GDI 实现杂志翻页动画效果时间 2010 01 13 blog csdn net 周公 说明 以前本人参与个一个电子杂志项目 当时要求实现模拟现实生活中的杂志翻页动画效果 别人推荐了这篇文章 最后达到了我想要的效果 今天尝试把这篇
  • 漫谈-Weblogic-CVE-2020-2555

    背景 2020年1月 互联网上爆出了weblogic反序列化远程命令执行漏洞 CVE 2020 2555 Oracle Fusion中间件Oracle Coherence存在缺陷 攻击者可利用该漏洞再未授权情况下通过构造T3协议请求 获取w
  • 软件测试面试题:HTTP和HTTPS协议区别?

    HTTP和HTTPS协议区别 https协议需要到CA Certificate Authority 证书颁发机构 申请证书 一般免费证书较少 因而需要一定费用 http是超文本传输协议 信息是明文传输 Https协议是由SSL和Http协议
  • 6 种易于上手的编程副业,每月赚取 1,000 多美元——没有废话

    没有自由职业者或博客 也不需要前期费用 你们中的大多数人阅读这样的故事是希望其中的一些故事能帮助您赚更多的钱 好吧 几年前我还是同一个人 我希望尝试一些新的副业并赚点钱 其中一个视频建议我在网上写作 此后我写了很多技术文章 在此过程中 我开
  • react结合js获取屏幕鼠标滚动等距离实现页面懒加载

    懒加载 也叫延迟加载 指的是在长网页中延迟加载内容或图像 是一种很好优化网页性能的方式 在滚动屏幕之前 可视化区域之外的内容不会进行加载 在屏幕滚动距离底部到一定距离时才加载 这样网页的加载速度更快 减少了服务器的负载 懒加载适用于图片较多
  • Python学习笔记第十一天(迭代器与生成器)

    Python学习笔记第十一天 迭代器与生成器 迭代器 StopIteration 生成器 结束语 迭代器与生成器 迭代器 迭代是Python最强大的功能之一 是访问集合元素的一种方式 迭代器是一个可以记住遍历的位置的对象 迭代器对象从集合的
  • linux启动service服务

    https medium com ameyadhamnaskar running java application as a service on centos 599609d0c641
  • Spark中的三种隐式转换

    1 使用SparkSQL中toDF时 import spark implicits 2 Spark整合Kudu 创建Kudu对象时 improt org apache kudu spark kudu 3 Spark中一些Scala类型转Ja
  • 每日一题:最长因子链

    最长因子链 题目 Daimayuan Online Judge 由于要找的数字不用按顺序 所以先给所有数排个序 找最长因子链类似于找最长上升子序列 用动态规划 状态划分 以第i个数结尾的因子链的倒数第二个数可能是第一个数 第二个数 第i 1
  • Settings sync 配置与使用

    1 settings sync 配置与使用 参考文章 我辈敢怀凌云志 参考文章 胖茄子 注意 settings sync 下载之后 一定要现在 忽略文件夹中 添加 History 忽略历史文件夹 1 获取令牌 步骤 点击 github 头像
  • 系统突然运行慢,线上CPU100%,频繁FullGC排查

    当然 这些问题最终导致的直观现象就是系统运行缓慢 并且有大量的报警 本文主要针对系统运行缓慢这一问题 提供该问题的排查思路 从而定位出问题的代码点 进而提供解决该问题的思路 对于线上系统突然产生的运行缓慢问题 如果该问题导致线上系统不可用