使用XPocket插件vmstat查看上下文切换原创
vmstat(VirtualMeomoryStatistics,虚拟内存统计)是Linux中监控内存的常用工具,可对操作系统的虚拟内存、进程、CPU等的整体情况进行监视,虽然此工具提供的是对整个系统的一个统计数字,但是能够为更详细的诊断方法指明排查方向。现在XPocket已经集成了原生的vmstat,在XPocket中运行vmstat命令后如下图所示。
vmstat默认给出了一次的统计结果,其中各个字段的含义如下图所示。
我们在进行性能分析与故障排查时,通常会使用vmstat来查看CPU上下文切换次数,因为上下文切换会不可避免地浪费CPU资源。那么什么是上下文切换呢?上下文切换指的是操作系统调度器移除当前正在运行的线程或任务,并将其替换为等待中的线程。上下文切换的示意图如下图所示。
那么什么时候会造成上下文切换呢?下面进行了一下简单的列举:
- CPU 时间片结束,CPU 是划分为多个时间片给不同进程使用的,当这些进程使用完时间片后就被系统调度器移除出去;
- 进程运行需要的资源不够,如等待IO、锁争用等;
- 进程主动挂起,如调用系统函数sleep等;
- 有优先级更高的进程执行,如硬中断。
对于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命令来查看上下文切换的次数,如下:
当我启动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后可以看到,系统的上下文切换增加了好几倍,如下:
在某个时间点,cs由104773一下增加到了373944,这就是我启动如上的Java程序造成的上下文切换次数突然增多。
虽然我们知道了哪些情况下会造成上下文切换,但是仅凭vmstat给出的cs上下文切换次数还不能够区分出到底是哪种情况,我们还需要结合XPocket中的其它集成工具综合查看,如线程锁争用时可通过JStack_X查看线程状态及锁的争用情况,使用线程剖析器查看阻塞代码。