The Complete Backend Engineer The Complete Backend Engineer
首页
  • Java基础

    • JavaScript
  • Java并发编程

    • 《JavaScript教程》
    • 浅谈Java并发安全发布技术
    • 浅谈Java线程池中拒绝策略与流控的艺术
    • 深入源码解析synchronized关键字
    • 浅谈Java并发编程中断的哲学
    • 深入理解Java中的final关键字
    • 深入剖析Java并发编程中的死锁问题
    • 浅谈池化技术的优雅关闭
    • synchronized关键字使用指南
    • 浅谈并发编程等待通知模型
    • 浅谈传统并发编程的优化思路
    • JS设计模式总结
  • JVM相关

    • 从零开始理解JVM的JIT编译机制
    • 简明的Arthas配置及基础运维教程
    • 基于Arthas Idea的JVM故障排查与指令生成
    • 基于arthas量化监控诊断java应用方法论与实践
    • 深入剖析arthas技术原理
  • 计算机组成原理

    • 浅谈CPU流水线的艺术
  • 操作系统

    • Linux性能问题排查最佳实践
    • Linux上IO性能问题的故障排除实践
    • 浅谈Linux权限管理
    • 从操作系统底层浅谈程序栈的高效性
  • CSS
  • 实用技巧与配置

    • Mac常用快捷键与效率插件指南
  • CSS
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

sharkchili

计算机禅修者
首页
  • Java基础

    • JavaScript
  • Java并发编程

    • 《JavaScript教程》
    • 浅谈Java并发安全发布技术
    • 浅谈Java线程池中拒绝策略与流控的艺术
    • 深入源码解析synchronized关键字
    • 浅谈Java并发编程中断的哲学
    • 深入理解Java中的final关键字
    • 深入剖析Java并发编程中的死锁问题
    • 浅谈池化技术的优雅关闭
    • synchronized关键字使用指南
    • 浅谈并发编程等待通知模型
    • 浅谈传统并发编程的优化思路
    • JS设计模式总结
  • JVM相关

    • 从零开始理解JVM的JIT编译机制
    • 简明的Arthas配置及基础运维教程
    • 基于Arthas Idea的JVM故障排查与指令生成
    • 基于arthas量化监控诊断java应用方法论与实践
    • 深入剖析arthas技术原理
  • 计算机组成原理

    • 浅谈CPU流水线的艺术
  • 操作系统

    • Linux性能问题排查最佳实践
    • Linux上IO性能问题的故障排除实践
    • 浅谈Linux权限管理
    • 从操作系统底层浅谈程序栈的高效性
  • CSS
  • 实用技巧与配置

    • Mac常用快捷键与效率插件指南
  • CSS
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 计算机组成原理

  • 操作系统

  • 运维

    • Linux性能问题排查最佳实践
      • 写在文章开头
      • 常见系统巡检流程
        • 查看系统基本运行信息
        • 查看内核是否存在报错
        • 查看虚拟内存使用情况
        • CPU亲和力巡检
        • 检测进程摘要
        • 查看内存使用情况
        • 查看I/O使用情况
        • 查看网卡使用情况
      • 详解生产环境常见问题
        • 应用程序延迟升高
        • 系统操作卡顿
        • 持续的偶发性系统卡顿问题排查
      • 小结
      • 参考
    • Linux上IO性能问题的故障排除实践
  • CSS

  • 计算机基础
  • 运维
sharkchili
2025-11-17
目录

Linux性能问题排查最佳实践

# 写在文章开头

本文整理了传统软件工程师必备的Linux系统性能问题排查的通用方法论和实践,将针对以下三个经典场景展开探讨:

  1. I/O性能瓶颈
  2. CPU飙升
  3. 偶发CPU飙升

同时考虑到笔者文章的受众面大部分都是Java开发人员,所以复现问题故障的例子也都采用Java进行编码部署复现,对应的示例也都会在案例排查的最后展开说明。 我是 SharkChili ,Java 开发者,Java Guide 开源项目维护者。欢迎关注我的公众号:写代码的SharkChili,也欢迎您了解我的开源项目 mini-redis:https://github.com/shark-ctrl/mini-redis。

为方便与读者交流,现已创建读者群。关注上方公众号获取我的联系方式,添加时备注加群即可加入。

# 常见系统巡检流程

# 查看系统基本运行信息

在正式介绍这些生产案例之前,我们先了解一些比较常见的系统巡检步骤,在日常监控巡检的时候,我们一般先查看系统的运行基本信息,所以我们优先会执行uptime查看系统整体运行情况和负载,以笔者的输出为例,可以看到uptime输出显示如下消息:

  1. 系统时间为23:08:23
  2. 当前系统运行1天4小时多
  3. 当前系统有两个用户登录
  4. 系统近1min、15min、30min的系统负载稳定在0

uptime指令可以反映一个时间段的系统运行负载,一般来说,若近1min的值远远低于15min和30min的值,这可能就说明我们错过了系统反馈的故障:

23:08:23 up 1 day,  4:02,  3 users,  load average: 0.00, 0.00, 0.00
1

# 查看内核是否存在报错

然后我们就通过dmesg |tail查看内核环形缓冲区的消息,通过该指令可以看到一些系统消息,检查运行时报错,以笔者服务器的输出结果来看:

  1. 网络连接初始化失败
  2. hv_balloon动态内存信息
  3. Time jumped backwards, rotating即因为某个原因系统向后跳动了
  4. 系统缓存清理
[    5.923407] WSL (206) ERROR: CheckConnection: getaddrinfo() failed: -5
[   48.414734] hv_balloon: Max. dynamic memory size: 8126 MB
[   48.590258] systemd-journald[53]: Time jumped backwards, rotating.
[  605.106029] TCP: eth0: Driver has suspect GRO implementation, TCP performance may be compromised.
[  676.028375] mini_init (190): drop_caches: 1
[ 1166.861172] mini_init (190): drop_caches: 1
[19373.591153] Adjusting hyperv_clocksource_tsc_page more than 11% (1467515026 vs 1862270976)
[48202.834581] mini_init (190): drop_caches: 1
[48988.179688] mini_init (190): drop_caches: 1
[81152.543659] mini_init (190): drop_caches: 1
1
2
3
4
5
6
7
8
9
10

# 查看虚拟内存使用情况

vmstat用于查看虚拟内存、I/O、CPU运行状态等指标,在进行巡检时我们一般使用vmstat 1进行每隔1秒一次的输出,以观察系统的实时状态。以笔者服务器为例,输出结果如下:

procs -----------memory---------- ---swap-- -----io---- -system-- -------cpu-------
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st gu
 0  0      0 7080860  24908 270132    0    0    14    20  520    0  0  0 100  0  0  0
 0  0      0 7080840  24908 270172    0    0     0     0  538  457  0  0 100  0  0  0
1
2
3
4

针对进程的输出参数procs:

  1. r: 运行队列中等待运行的进程数,如果该值持续大于CPU核心数,说明CPU资源紧张
  2. b: 等待I/O完成的进程数,如果该值持续较高,说明存在I/O瓶颈

针对内存的参数组memory:

  1. swpd: 已使用的交换空间大小,如果该值持续增长,说明物理内存不足
  2. free: 空闲的物理内存大小
  3. buff: 用于文件系统缓冲的内存大小
  4. cache: 用于缓存文件数据的内存大小

针对磁盘I/O参数组:

  1. bi/bo: 每秒从磁盘读取/写入的数据块数
  2. si/so: 每秒从磁盘交换进/出内存的数据块数,如果这两个值持续大于0,说明内存不足

CPU参数组:

  1. us: 用户态CPU使用百分比
  2. sy: 内核态CPU使用百分比
  3. id: CPU空闲百分比
  4. wa: 等待I/O完成的CPU时间百分比,如果该值持续较高,说明存在I/O瓶颈

从以上输出可以看出,系统当前状态良好:CPU空闲率100%,无进程等待I/O,无内存交换发生。

# CPU亲和力巡检

CPU亲和力是指进程或线程绑定到特定CPU核心的特性。通过检查各CPU核心的使用率分布,可以判断是否存在CPU负载不均衡的情况。执行mpstat -P ALL 1可以查看每个CPU核心的使用情况。

从笔者的输出结果来看,各CPU核心的使用率分布相对均匀,没有出现某个CPU核心使用率异常飙升的情况。如果某个CPU核心使用率持续接近100%,而其他核心相对空闲,则可能说明存在以下问题:

  1. 单线程应用无法充分利用多核CPU资源
  2. 进程调度问题导致负载集中在特定核心
  3. 某个线程在特定核心上出现死循环或计算密集型任务

从平均值来看,系统整体CPU使用率较低,各核心负载分布相对均衡,说明CPU资源利用合理。

# 检测进程摘要

pidstat命令用于监控进程的CPU、内存、I/O等资源使用情况。与top命令不同,pidstat不会清屏刷新,而是以滚动方式显示进程信息,便于观察一段时间内的进程负载变化。执行pidstat 1可以每秒输出一次进程统计信息。

输出字段说明:

  • UID: 进程所属用户ID
  • PID: 进程ID
  • %usr: 进程在用户态消耗的CPU百分比
  • %system: 进程在内核态消耗的CPU百分比
  • %guest: 进程在虚拟机中消耗的CPU百分比
  • %wait: 进程等待CPU的时间百分比
  • %CPU: 进程总的CPU使用百分比
  • CPU: 进程当前运行的CPU核心编号
  • Command: 进程命令名称

从输出结果可以看出:

  1. 当前系统运行java、Nightingale、frpc、mysqld等多个进程,且均匀分布在各个cpu
  2. 每个进程用户态usr和system内核态cpu使用率都不高,并不算很活跃或者说存在异常

# 查看内存使用情况

free命令用于查看系统内存使用情况,包括物理内存和交换空间的使用统计。一般情况下我们使用free -m以MB或者为单位查看内存使用情况,当然我们也可以通过free -h易读模式查看内存使用情况:

输出字段说明:

  • total: 内存总量
  • used: 已使用的内存
  • free: 空闲的内存
  • shared: 多个进程共享的内存
  • buff/cache: 用于缓冲和缓存的内存
  • available: 可用内存

从输出结果可以看出:

  1. 物理内存总量为7G,已使用接近4G,空闲3G,内存使用率较低
  2. buff/cache占用582MB,这部分内存可以在需要时被回收
  3. available内存为4G左右,说明系统有充足的可用内存
  4. 交换空间总量2G,已使用356MB,存在内存空间置换,需要注意

需要注意的是,Linux系统会尽可能利用空闲内存作为文件系统缓存,因此free值较低并不一定表示内存不足。应重点关注available字段和交换空间使用情况来判断内存是否充足。

# 查看I/O使用情况

iostat命令用于监控系统设备的I/O负载情况。使用iostat -xz 1可以详细查看磁盘的读写性能指标,其中:

  • -x: 显示扩展统计信息
  • -z: 跳过无活动的设备
  • 1: 每秒刷新一次

主要监控指标包括:

  1. r/s、w/s: 每秒读/写请求数
  2. rkB/s、wkB/s: 每秒读/写的数据量(KB)
  3. await: I/O请求的平均等待时间(ms),包括排队时间和处理时间
  4. avgqu-sz: 平均I/O请求队列长度,如果大于1说明设备可能已饱和
  5. util: 设备使用率百分比,如果持续高于80%说明设备可能成为瓶颈

从输出结果可以看出:

  1. 各磁盘设备的I/O请求很少,r/s和w/s值都很低
  2. await值都很小,说明I/O响应时间很短
  3. util值都很低,说明磁盘设备使用率很低,没有出现瓶颈
  4. avgqu-sz值都小于1,说明I/O请求队列长度正常

整体来看,系统磁盘I/O性能良好,没有出现性能瓶颈。

# 查看网卡使用情况

sar命令可以监控网络接口的流量情况。使用sar -n DEV 1可以每秒输出网络接口的统计信息。

输出字段说明:

  • IFACE: 网络接口名称
  • rxpck/s: 每秒接收的数据包数量
  • txpck/s: 每秒发送的数据包数量
  • rxkB/s: 每秒接收的数据量(KB)
  • txkB/s: 每秒发送的数据量(KB)
  • %ifutil: 网络接口使用率百分比

从输出结果可以看出:

  1. lo接口是本地回环接口,用于本机通信
  2. eth0是实际的网络接口
  3. 数据包收发量和数据流量都比较小
  4. %ifutil值为0,说明网络接口使用率很低,没有出现网络瓶颈

为了更准确地判断网络性能,还需要结合网络带宽信息进行分析。

网络I/O角度排查问题,这里笔者也建议采用nload进行网络资源诊断,如果没有下载的可以自行通过yum或者apt的方式自行下载,这里笔者也贴出ubuntu服务器的下载指令:

# ubuntu下载安装nload
sudo apt install nload -y
1
2

键入nload实时输出网络带宽的使用情况,可以看到:

  1. 输入流量(incoming):也就是进入网卡的流量,即下载流量,当前下载速度约为20KB/s,一般认为只有当网速接近最大带宽时才说明带宽使用率接近饱和,本次输出看起来已经无限接近max了
  2. 输出流量(outgoing)即从网卡出去的流量,也就是上传流量

对应网卡指标详情为:

  1. 当前流量
  2. 平均流量
  3. 最小流量
  4. 最大流量
  5. ttl总和

# 详解生产环境常见问题

# 应用程序延迟升高

第一个案例是用户反馈系统延迟升高,网卡打开缓慢。从开发者的角度一定要明白,所有表现为卡顿、延迟的原因很大概率是系统资源吃紧,只有在资源分配不足的情况下,才会导致程序运行阻塞,无法及时处理用户的请求。

关于服务器的阈值指标,按照业界通用的经验,对应CPU和内存的负载要求的合理上限为:

  1. CPU使用率控制在75%左右
  2. 内存使用率控制在80%以内
  3. 虚拟内存尽可能保持在0%
  4. 负载不超过CPU核心数的75%

笔者一般会先通过top查看操作系统的CPU利用率,这里笔者因为是个人服务器原因则采用更强大、更直观的htop查看个人服务器的资源使用情况,对应的安装指令如下:

sudo apt update
sudo apt install htop
1
2

而本次htop输出的指标如下:

  1. 服务器为6核,对应的CPU使用率分别是3.9、0、0、2.7、0.7、1.4,按照业界的通用标准,当前服务器各核心CPU使用率较低,但需结合系统负载综合判断
  2. Mem代表了内存使用率,内存一般情况下是用于存储程序代码和数据,分为物理内存和虚拟内存,物理内存显示内存接近8G仅用了1G不到,使用率不到80%,说明资源冗余
  3. Swp显示交换空间即虚拟内存的使用情况,可以看到也仅仅用了32M,并没有大量的内存数据被置换到交换空间,结合第2点来看,内存资源充足
  4. Tasks显示进程数和线程数一共有35个进程,这35个进程对应100个线程处理,Kthr显示指标为0说明有0个内核线程,而Running为1说明有一个用户进程在运行
  5. 而系统平均负载近1分钟为4.96,按照业界标准CPU核心数*0.75作为系统负载的运算阈值,如果超过这个值则说明系统处于高负载状态,很明显我们的6核服务器系统负载偏高了

综合来看,服务器系统负载偏高但各CPU核心使用率较低,结合内存使用情况,问题可能出现在I/O资源等待上,此时我们就要从I/O资源角度进一步排查问题:

我们从I/O资源排查入手,通过vmstat 1执行每秒一次的监控指标输出,以笔者的服务器为例,可以看到如下几个指标:

  1. r:按照文档解释为The number of runnable processes (running or waiting for run time)即正在运行或等待运行的进程数,如果大于CPU核心数则说明CPU处于过载状态,而当前服务器这个值为0,说明队列处理状态良好
  2. b: 按照文档解释为The number of processes blocked waiting for I/O to complete即等待I/O完成的进程数,从参数b可以看出有大量进程等待I/O,说明当前服务器存在I/O瓶颈。
  3. swpd: the amount of swap memory used即交换空间也就是虚拟内存的使用,而当前服务器已被使用30468说明已经在使用交换空间,由此参数结合buff(缓存中尚未写入磁盘的内容)和cache(从磁盘加载出来的缓存数据)来看,当前内存资源持续升高,存在读写虚拟内存的情况,存在I/O性能瓶颈。
  4. 从bo来看有大量任务进行每秒写块
  5. 针对CPU一个板块输出的us(用户代码执行时间)、sy(内核执行时间)、id(空闲时间)、wa(等待I/O的时间),其中wa即等待I/O的时间持续处于一个高数值的状态,更进一步明确CPU在空转,等待I/O完成,而I/O资源处于吃紧的状态

考虑为I/O资源瓶颈,我们优先从网络I/O角度排查问题,这里笔者采用nload进行网络资源诊断

键入nload实时输出网络带宽的使用情况,可以看到:

  1. 输入流量(incoming)即下载流量,当前下载速度约为1KB/s,占最大带宽的20%左右,一般认为只有当网速接近最大带宽时才说明带宽使用率接近饱和
  2. 输出流量(outgoing)即上传流量,同理当前也仅仅使用8%,也未达到饱和的阈值

所以I/O资源吃紧的问题原因并非网络I/O,我们需要进一步从服务器磁盘I/O角度进一步排查:

所以从这些指标来看,存在大量的线程在等待I/O资源的分配而进入阻塞,所以笔者基于iostat -x 1使每一秒都输出更详细的信息,可以看到sdd盘对应的磁盘忙碌百分比util基本跑满100%,已基本接近饱和,此时基本是确认有大量线程在进行I/O读写任务了,且查看I/O读写指标:

  1. 每秒读写的吞吐量w/s为175
  2. 每秒写入wkB/s的172704KB
  3. SSD盘util即I/O资源利用率为100%,已经远超业界阈值60%,说明存在I/O性能瓶颈,需要补充说明的是当CPU和I/O设备达到100%利用率时,都可能导致进程阻塞,但I/O设备的处理机制与CPU调度不同,在高负载情况下更容易导致大量I/O请求排队等待
  4. 写请求的平均等待时间w_await为5151ms

换算下来172704KB/175每秒写入的速率为987KB每秒,由此可确定因为磁盘性能读写性能瓶颈导致大量I/O读写任务阻塞,进而导致服务器卡顿,用户体验差:

所以,对于系统延迟严重的情况,整体排查思路为:

  1. 通过top指令查看CPU使用率,若正常进入步骤2
  2. 基于vmstat查看内存使用率和I/O资源情况
  3. 基于nload查看网络I/O使用情况
  4. 基于iostat查看网络I/O和磁盘I/O情况最终确认问题

本例子的最后笔者也给出本次故障问题的示例代码:

 /**
     * 启动磁盘I/O操作以模拟高I/O负载
     * 通过创建多个I/O任务线程来模拟高磁盘I/O负载
     */
    private static void startDiskIOOperations() {
        log.info("开始高I/O磁盘操作...");
        log.info("在另一个终端中运行 'iostat -x 1' 来监控磁盘利用率。");
        
        // 创建固定线程数的线程池
        executor = Executors.newFixedThreadPool(NUM_THREADS);
        
        // 提交多个任务以连续写入磁盘
        for (int i = 0; i < NUM_THREADS; i++) {
            executor.submit(new IOTask(i));
        }
        
        log.info("磁盘I/O操作已启动,使用 {} 个线程", NUM_THREADS);
    }


/**
     * 执行连续写入操作以模拟高I/O的任务
     * 该类负责执行磁盘I/O操作,通过不断写入和清空文件来模拟高I/O负载
     */
    static class IOTask implements Runnable {
        private final int taskId;
        
        public IOTask(int taskId) {
            this.taskId = taskId;
        }
        
        @Override
        public void run() {
            // 每个线程写入自己的临时文件
            String filename = "/tmp/disk_io_test_" + taskId + ".tmp";
            
            try (FileOutputStream fos = new FileOutputStream(filename)) {
                log.info("线程-{} 正在写入 {}", taskId, filename);
                
                // 连续将数据写入文件并在每次写入后清空文件
                while (!Thread.currentThread().isInterrupted()) {
                    performDiskIOOperation(fos, taskId);
                    ThreadUtil.sleep(500);
                }
            } catch (IOException e) {
                log.error("线程-{} 发生错误: {}", taskId, e.getMessage());
            }
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

# 系统操作卡顿

第二个例子也同样是用户反馈系统操作卡顿感严重,整体点击响应非常慢,我们还是考虑资源吃紧,优先使用top指令查看资源使用情况,从top指令来看:

  1. 输出us查看各个核心的CPU使用率跑满
  2. 系统平均负载基本超过70%(6*0.7)已经超过4.2

这就是经典的计算密集型任务跑满所有线程的经典例子

一般针对CPU跑满的问题,笔者一般会通过mpstat -P ALL 1查看CPU时间片是否分配均衡,是否出现偏斜导致CPU过热的情况,例如所有运算任务都往一个CPU核心上跑,经过笔者每秒1次的输出持续观察来看,整体资源吃紧,但并没有出现资源分配偏斜的情况,同时内存资源使用率也不高,也没有大量的iowait等待:

结合第一步top指令定位到的进程是Java进程,笔者索性通过Arthas直接定位故障代码,首先通过thread锁定问题线程,可以看到pool-前缀的线程基本都是跑满单个CPU,所以我们就可以通过thread id查看线程的栈帧:

最终锁定到了这段代码段,即一个密集的循环运算的线程:

对应的笔者也贴出故障代码段示例,来总结一下系统使用卡顿的排查思路:

  1. 基本top查看用户态和内核态CPU使用率
  2. 用户态使用率偏高,通过mpstat查看CPU使用是否偏斜,是否保证CPU亲和力
  3. 如果CPU使用没有出现偏斜,则直接通过问题定位到Java进程,结合Arthas快速定位问题线程进行诊断
 /**
     * 模拟CPU使用率过高的情况
     * 通过创建多个CPU密集型任务线程来模拟高CPU使用率
     */
    public static void startHighCPUUsage() {
        log.info("开始模拟高CPU使用率...");
        
        // 创建CPU密集型任务的线程池
        ExecutorService cpuExecutor = Executors.newFixedThreadPool(NUM_THREADS);
        
        // 提交多个CPU密集型任务
        for (int i = 0; i < NUM_THREADS; i++) {
            cpuExecutor.submit(new CPUIntensiveTask(i));
        }
        
        log.info("高CPU使用率模拟已启动,使用 {} 个线程", NUM_THREADS);
    }
 /**
     * CPU密集型任务,用于模拟高CPU使用率
     * 该类通过执行复杂的数学计算来占用CPU资源,从而模拟高CPU使用率场景
     */
    static class CPUIntensiveTask implements Runnable {
        private final int taskId;
        
        public CPUIntensiveTask(int taskId) {
            this.taskId = taskId;
        }
        
        @Override
        public void run() {
            log.info("CPU密集型任务-{} 已启动", taskId);
            
            // 执行CPU密集型计算以提高CPU使用率
            while (!Thread.currentThread().isInterrupted()) {
                // 执行一些复杂的数学计算
                double result = 0;
                for (int i = 0; i < 1000000; i++) {
                    result += Math.sqrt(Math.log(i + 1) * Math.cos(i) * Math.sin(i));
                }
                log.debug("CPU任务-{} 完成一轮计算,结果: {}", taskId, result);
            }
            
            log.info("CPU密集型任务-{} 已结束", taskId);
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

# 持续的偶发性系统卡顿问题排查

此类问题比较棘手,系统偶发卡顿意味着是瞬时、频繁的资源吃紧,我们还是直接使用top指令无法明确立刻捕捉到进程,可能刚刚一看到飙升的进程就消失了。

同理使用mpstat、vmstat指令无法准确定位到超短期飙升问题的进程,而基于iostat也没有看到明显的I/O资源吃紧,所以我们可以采用perf指令解决问题,以笔者的Ubuntu服务器为例,对应的安装步骤:

# 安装perf工具
sudo apt install linux-tools-generic
sudo apt install linux-tools-common

1
2
3
4

在笔者完成安装并启动之后,系统抛出WARNING: perf not found for kernel xxxx的异常,对应的解决方案是要主动安装linux-tools-generic并定位到linux-tools目录下找到自己的generic版本完成符号链接,以笔者本次安装为例就是6.8.0-79:


sudo ln -s /usr/lib/linux-tools/6.8.0-79-generic/perf /usr/bin/perf
1
2

完成上述安装之后,我们就可以通过将频率降低设置为99并将监控结果导出到tmp目录下的perf.data中:

sudo perf record -F 99 -a -g -o /tmp/perf.data sleep 10
1

可能很多读者好奇为什么需要将频率设置为99Hz,这样做的目的是为了避免与系统定时器中断频率(通常为100Hz)同步,从而避免锁步采样导致的偏差。

锁步采样是指采样频率与系统定时器中断频率相同或成倍数关系时,采样点会固定在相同的时间位置上,导致采样结果不能准确反映系统整体的性能状况。

使用99Hz这样的素数频率可以减少与系统周期性活动同步的概率,从而获得更全面、更准确的性能数据。

举个简单的例子,若我们试图确定道路是否出现拥堵,且通过24h一次的抽检查,那么当前样本就可能与交通保持一个平行的同步状态,例如:

  1. 交通车流情况在每天8点-12点拥堵,而我们的程序也是恰好在每天9点采集,那么它就会认为交通情况异常拥堵
  2. 若每天14点进行一次采集那么就避开了交通阻塞的高峰期则会得到一个相反的、也是不正确的结论

为了规避相同周期频率导致的lockstep即锁同步采样,我们可以适当降低频率避免与交通周期时间同步,保证一天的数据能够在一个周期内被完整地采集到,而本例最好的做法就是将定时间隔改为23h,这样一来每个23天的样本周期就会得到一天中所有时间的数据就能做到全面地了解到交通情况:

等待片刻后perf指令就会将结果输出到perf.data目录下:

[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.701 MB /tmp/perf.data (586 samples) ]
1
2

此时,通过sudo perf report查看报告,可以看到一个pid为1115751的Java进程对应线程CPU使用率飙升到86,此时我们就可以基于这条信息到指定的进程上查看该线程是否存在密集的运算:

最后我们也给出本示例的问题代码:

/**
     * 模拟CPU瞬间飙高然后降低的情况
     * 实现每10秒一次的CPU使用率飙高和降低循环(仅使用单核)
     */
    public static void startCPUSpikeAndDrop() {
        log.info("开始模拟CPU瞬间飙高然后降低...");
        
        // 创建用于CPU飙高的线程池(仅使用单核)
        ExecutorService spikeExecutor = Executors.newFixedThreadPool(1);
        
        // 提交单个CPU密集型任务来制造飙高
        spikeExecutor.submit(new CPUSpikeTask(0));
        
        log.info("CPU瞬间飙高已启动,使用 {} 个线程", 1);
        
        // 每隔10秒切换CPU飙高状态,实现周期性飙高和降低
        Thread spikeController = new Thread(() -> {
            boolean isSpiking = true;
            ExecutorService currentExecutor = spikeExecutor;
            
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    // 等待10秒
                    Thread.sleep(10000);
                    
                    if (isSpiking) {
                        // 停止当前的CPU飙高任务
                        currentExecutor.shutdownNow();
                        log.info("CPU使用率已降低");
                    } else {
                        // 启动新的CPU飙高任务
                        currentExecutor = Executors.newFixedThreadPool(1);
                        currentExecutor.submit(new CPUSpikeTask(0));
                        log.info("CPU使用率已飙高");
                    }
                    
                    // 切换状态
                    isSpiking = !isSpiking;
                } catch (InterruptedException e) {
                    log.error("CPU飙高控制线程被中断", e);
                    break;
                }
            }
        });
        
        spikeController.setDaemon(true);
        spikeController.start();
    }
 /**
     * CPU瞬间飙高任务,用于模拟CPU瞬间飙高然后降低的情况
     * 该类通过执行密集的数学计算来模拟CPU使用率的瞬时飙高,并在指定时间后自动停止
     */
    static class CPUSpikeTask implements Runnable {
        private final int taskId;
        
        public CPUSpikeTask(int taskId) {
            this.taskId = taskId;
        }
        
        @Override
        public void run() {
            log.info("CPU瞬间飙高任务-{} 已启动", taskId);
            
            // 执行空循环以提高CPU使用率
            while (!Thread.currentThread().isInterrupted()) {
                // 空循环消耗CPU
            }
            
            log.info("CPU瞬间飙高任务-{} 已结束", taskId);
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71

# 小结

本文针对应用延迟、系统卡顿、偶发频繁卡顿三种常见的系统故障给出通用普适的排查思路,整体来说此类问题归根结底都是系统资源吃紧,需要找到饱和的资源结合代码推测根源并制定修复策略,以本文为例,通用的排查思路都为:

  1. 基于top查看CPU、内存、负载
  2. 若CPU未饱和则通过vmstat查看I/O资源使用情况
  3. 明确I/O瓶颈通过nload和iostat查询是网络I/O还是磁盘I/O
  4. 若上述排查都无果,且CPU负载偶发飙高,可通过perf并调整频率监控系统定位系统中异常运行的资源
  5. 结合上述推断结果查看是否是异常消耗,如果是则优化代码,反之结合情况增加硬件资源

此外,对于内存相关问题,还可以通过以下方式进一步诊断:

  • 使用ps命令查看进程的内存使用情况,特别关注RSS(常驻内存)和VSZ(虚拟内存)字段
  • 使用pmap命令查看进程的内存映射情况,识别是否存在异常的内存段
  • 使用valgrind等工具检测C/C++程序的内存泄漏问题
  • 对于Java应用,可结合jstat、jmap等工具分析堆内存使用情况

我是 SharkChili ,Java 开发者,Java Guide 开源项目维护者。欢迎关注我的公众号:写代码的SharkChili,也欢迎您了解我的开源项目 mini-redis:https://github.com/shark-ctrl/mini-redis。

为方便与读者交流,现已创建读者群。关注上方公众号获取我的联系方式,添加时备注加群即可加入。

# 参考

Linux perf: 为什么采样频率设置为99Hz而不是100Hz?:https://blog.csdn.net/juS3Ve/article/details/78651436 (opens new window) iostat命令详解与性能分析指南:https://blog.csdn.net/qq_42895490/article/details/147196569 (opens new window) linux进阶篇:性能监控工具——vmstat命令详细讲解:https://blog.csdn.net/qq_39241682/article/details/137705599 (opens new window) 【双语视界】Linux 性能问题排查指南:https://www.bilibili.com/video/BV14139zrEC2/?buvid=Y942A991A168E31D4296B54F9D22E6B8C782&from_spmid=tm.recommend.0.0&is_story_h5=false&mid=3URzpfGDRykbWghI4MoSYg%3D%3D&plat_id=116&share_from=ugc&share_medium=iphone&share_plat=ios&share_session_id=1869E0A1-51E1-4D1C-BC30-FEC4007499FA&share_source=WEIXIN&share_tag=s_i&spmid=united.player-video-detail.0.0&timestamp=1754098287&unique_k=VFHSxEY&up_id=372185561&vd_source=bf04f9a485aa892c0242fbfdfca25589 (opens new window) 服务器性能如何优化?(建议收藏):https://mp.weixin.qq.com/s/39XvWcmbT0DTTBuwksoDPg (opens new window) 一文吃透Linux htop命令:https://www.cnblogs.com/TangQF/articles/18019495 (opens new window) 详解 htop,如何使用 htop 命令监视系统进程和资源使用:https://www.shejibiji.com/archives/9635 (opens new window) linux perf安装问题解决:https://blog.csdn.net/seaneer/article/details/144194621 (opens new window) Linux buffer/cache 内存占用过高的原因以及解决办法:https://blog.csdn.net/kunyus/article/details/104617426 (opens new window) linux性能监控:CPU监控命令之vmstat命令:https://zhuanlan.zhihu.com/p/162711990 (opens new window) Linux Performance Analysis in 60,000 Milliseconds:https://netflixtechblog.com/linux-performance-analysis-in-60-000-milliseconds-accc10403c55 (opens new window) Linux下dmesg命令处理故障和收集系统信息的7种用法:https://www.cnblogs.com/duanxz/p/3477095.html (opens new window) nload命令详解:网络流量实时监控带宽工具:https://www.fujieace.com/linux/nload.html (opens new window)

编辑 (opens new window)
上次更新: 2025/12/16, 14:07:43
从操作系统底层浅谈程序栈的高效性
Linux上IO性能问题的故障排除实践

← 从操作系统底层浅谈程序栈的高效性 Linux上IO性能问题的故障排除实践→

最近更新
01
从操作系统底层浅谈程序栈的高效性
01-07
02
从零开始理解JVM的JIT编译机制
01-06
03
浅谈Linux权限管理
12-26
更多文章>
Theme by Vdoing | Copyright © 2025-2026 Evan Xu | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×
×