性能文章>使用XPocket插件vmstat查看上下文切换>

使用XPocket插件vmstat查看上下文切换原创

https://a.perfma.net/img/2382850
9月前
196802

vmstat(VirtualMeomoryStatistics,虚拟内存统计)是Linux中监控内存的常用工具,可对操作系统的虚拟内存、进程、CPU等的整体情况进行监视,虽然此工具提供的是对整个系统的一个统计数字,但是能够为更详细的诊断方法指明排查方向。现在XPocket已经集成了原生的vmstat,在XPocket中运行vmstat命令后如下图所示。
image.png
vmstat默认给出了一次的统计结果,其中各个字段的含义如下图所示。
image.png
我们在进行性能分析与故障排查时,通常会使用vmstat来查看CPU上下文切换次数,因为上下文切换会不可避免地浪费CPU资源。那么什么是上下文切换呢?上下文切换指的是操作系统调度器移除当前正在运行的线程或任务,并将其替换为等待中的线程。上下文切换的示意图如下图所示。
image.png
那么什么时候会造成上下文切换呢?下面进行了一下简单的列举:

  1. CPU 时间片结束,CPU 是划分为多个时间片给不同进程使用的,当这些进程使用完时间片后就被系统调度器移除出去;
  2. 进程运行需要的资源不够,如等待IO、锁争用等;
  3. 进程主动挂起,如调用系统函数sleep等;
  4. 有优先级更高的进程执行,如硬中断。

对于Java开发者来说,通常写的Java程序会造成上下文频繁切换的原因最可能是如上的第2点和第3点,我们可以分别写2个简单的Java实例模拟一下这2种情况,然后利用XPocket的vmstat插件工具观察上下文切换次数。

首先我们模拟一下由于等待IO造成的上下文切换。先创建一个SpringBoot项目,简单编写一个对外提供服务的Controller,代码如下:

@RestController
public class UserController implements Serializable {
    @RequestMapping("/list")
    public String list(){
        return "OK";
    }
}

然后我们用一个线程不断的发请求,代码如下:

public static void main(String[] args) {
    for (int i = 0; i < 10000000; ++i) {
        String url = "http://localhost:8080/list";
        sendRequest(url, "POST");
    }
}
 
public static String sendRequest(String urlParam, String requestType) {
    HttpURLConnection con = null;
    BufferedReader buffer = null;
    StringBuffer resultBuffer = null;
 
    try {
        URL url = new URL(urlParam);
        con = (HttpURLConnection) url.openConnection();
        con.setRequestMethod(requestType);
        con.setRequestProperty("Content-Type", "application/json;charset=GBK");
        con.setDoOutput(true);
        con.setDoInput(true);
        con.setUseCaches(false);
        int responseCode = con.getResponseCode();
        if (responseCode == HttpURLConnection.HTTP_OK) {
            InputStream inputStream = con.getInputStream();
            resultBuffer = new StringBuffer();
            String line;
            buffer = new BufferedReader(new InputStreamReader(inputStream, "GBK"));
            while ((line = buffer.readLine()) != null) {
                resultBuffer.append(line);
            }
            return resultBuffer.toString();
        }
 
    } catch (Exception e) {
        e.printStackTrace();
    }
    return "";
}

我们可以使用vmstat 1命令来查看上下文切换的次数,如下:
image.png
当我启动main()方法后只运行一个线程来发送请求,但是上下文的切换一下子多了几倍,这不可能是CPU 时间片结束、或者主动挂起或中断造成的,所以只能是IO等待,因为网络传输会涉及到IO交互,Java底层的实现会调用系统函数来处理。

我们再看一个进程频繁主动挂起的例子,如下:


import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;
 
public final class ContextSwitchTest {
    static AtomicReference<Thread> turn = new AtomicReference<Thread>();
 
    static final class WorkerThread extends Thread {
        volatile Thread other;
        volatile int nparks;
 
        public void run() {
            final AtomicReference<Thread> t = turn;
            final Thread other = this.other;
            for (int i = 0; i < 100000000; ++i) {
                while (!t.compareAndSet(other, this)) {
                    LockSupport.park();
                }
                LockSupport.unpark(other);
            }
        }
    }
 
    public static void main(String[] args) throws Exception {
        WorkerThread a = new WorkerThread();
        WorkerThread b = new WorkerThread();
        a.other = b;
        b.other = a;
        turn.set(a);
        a.start();
        b.start();
        a.join();
        b.join();
    }
}

这次是线程频繁切换的例子,调用LockSupport.park()会主动让出CPU的使用权。再次运行vmstat 1后可以看到,系统的上下文切换增加了好几倍,如下:
image.png
在某个时间点,cs由104773一下增加到了373944,这就是我启动如上的Java程序造成的上下文切换次数突然增多。

虽然我们知道了哪些情况下会造成上下文切换,但是仅凭vmstat给出的cs上下文切换次数还不能够区分出到底是哪种情况,我们还需要结合XPocket中的其它集成工具综合查看,如线程锁争用时可通过JStack_X查看线程状态及锁的争用情况,使用线程剖析器查看阻塞代码。

分类:标签:
请先登录,感受更多精彩内容
快去登录吧,你将获得
  • 浏览更多精彩评论
  • 和开发者讨论交流,共同进步

为你推荐

在调试器里看LINUX内核态栈溢出
图灵最先发明了栈,但没有给它取名字。德国人鲍尔也“发明”了栈,取名叫酒窖。澳大利亚人汉布林也“发明”了栈,取名叫弹夹。1959年,戴克斯特拉在度假时想到了Stack这个名字,后来被广泛使用。
LONG究竟有多长,从皇帝的新衣到海康SDK
转眼之间初中毕业30年了,但我仍清楚的记得初中英语的一篇课文,题目叫《皇帝的新装》(“The king’s new clothes”)。这篇课文的前两句话是:”Long long ago, there
雕刻在LINUX内核中的LINUS故事
因为LINUX操作系统的流行,Linus 已经成为地球人都知道的名人。虽然大家可能都听过钱钟书先生的名言:“假如你吃个鸡蛋觉得味道不错,又何必认识那个下蛋的母鸡呢?” 但是如果真是遇到一个“特别显赫”
从猫蛇之战再看内核戏CPU
连续写了几天的代码,有些疲倦,吃过晚饭,换个工作方式,继续和大家聊猫蛇之战。蛇不仅丑陋,而且可能伤人害命,是邪恶的象征。猫与蛇战,代表着讨伐奸邪,是正义之战。猫与蛇战,技艺娴熟,举重若轻,叫人拍手叫绝
如何使用Linux内核中没有被导出的变量或函数?
本文详细介绍了使用EXPORT_SYMBOL宏导出函数或变量、使用kallsyms_lookup_name()查找函数或变量的虚拟地址以及内核模块中直接使用内核函数的虚拟地址等3种方案解决没有被EXPORT_SYMBOL 相关的宏导出的变量或函数不能直接使用的问题
LINUX网络子系统中DMA机制的实现
我们先从计算机组成原理的层面介绍DMA,再简单介绍Linux网络子系统的DMA机制是如何的实现的。 计算机组成原理中的DMA 以往的I/O设备和主存交换信息都要经过CPU的操作。不论是最早的轮询方式,
那些吃CPU的大户
最近沉浸在代码的世界里,处于两耳不闻窗外事的状态。今晚偶有闲暇,看了一眼任务管理器,IDLE进程的时间居然有800多小时,还有多个进程的CPU净时间达到小时级别。如下图所示,IDLE进程的CPU净时间
内存泄漏(增长)火焰图
本文总结了在分析内存增长和内存泄漏问题用到的4种追踪方法得到有关内存使用情况的代码路径,使用栈追踪技术对代码路径进行检查,并且会以火焰图的形式把它们可视化输出,在Linux上演示分析过程,随后概述其它系统的情况。