相关历史文章(阅读本文前,您可能需要先看下之前的系列?)
国内最全的Spring Boot系列之三
2020上半年发文汇总「值得收藏」
GraphQL的探索之路 – SpringBoot集成GraphQL小栗子篇二 - 第315篇
GraphQL的探索之路 – SpringBoot集成GraphQL之Query篇三 - 第316篇
GraphQL的探索之路 – SpringBoot集成GraphQL之Mutation篇四 - 第317篇
RocketMQ安装Linux/Mac/Window - 第318篇
需求缘起
在群里有这么一段对话:
愿得一人心:服务器cpu load偏高,无从下手,哪位大佬能提供点儿帮助
不老神话:top 查看偏高的进程
老鼠爱上猫:百度谷歌啊 查问题也是程序员必备技能之一
莫欺少年穷:要相信你肯定不是第一个遇到这个问题的
愿得一人心:查了,不顶用。
问问题真的把问题说清楚,不然解答的人也是一脸懵逼,很多人都是愿意解答问题的,但是问问题的人问的模棱两可,导致没有人敢解答。
正文开始
悟纤:师傅,师傅,紧急求助。
师傅:徒儿,何事如此之着急?
悟纤:我发现我写的代码导致CPU持续为99%,但是项目路这么大,我也不知道是哪块代码导致的。
师傅:徒儿,那你得看看是哪个线程里的逻辑导致了CPU飙高。
悟纤:那我们怎么找到这个线程在运行的堆栈信息呐?
师傅:jstack呀,你难道没有听过嘛?
悟纤:知道到知道这个指令,但是查看了些资料,都是说的不清楚,看完我也是一塌糊涂呐。
师傅:看来得为师给你好好讲讲了。
悟纤:还是喜欢师傅的讲解方式,简单、详细、一语道破天机。
一、排查步骤
师傅:要找回线程的堆栈信息,主要是利用java给我们提供的调优工具jstack,我们看下具体的一个步骤:
(1)使用命令top -p ,显示你的java进程的cpu情况,pid是你的java进程号,比如14203。(使用jps可以获取到java的进程id 或者top直接查看)
(2)按H,获取每个线程的CPU情况。(shirt+H)
(3)找到内存和cpu占用最高的线程tid,比如14204。
(4)转为十六进制得到 377C ,此为线程id的十六进制表示。
(5)执行 jstack |grep -A 10 ,得到线程堆栈信息中1371这个线程所在行的后面10行。(注意:如果十六进制由字母的要小写)
# Jstack 14203 | grep -A 10 377c
(6)查看对应的堆栈信息找出可能存在问题的代码。
师傅:看起来是不是很复杂,描述的罗里吧嗦的。
悟纤:看着就晕头转向的。
师傅:一句话概述。
一句话:通过top找到线程id,通过jstack找到线程的堆栈信息。
二、小试牛刀:Linux环境
2.1 准备工作
我们编写一个小代码EndlessLoopTest用于模拟导致cpu过高(源码在最后提供),编译成class文件,将我们的class文件放到Linux上。
注意:存放的需要根据包名建立目录结构,否则无法运行此class文件。
比如包名是com.kfit.jvm那么就需要创建一个目录结构com/kfit/jvm,然后把EndlessLoopTest.class放到这里面。
执行class:
java com.kfit.jvm.EndlessLoopTest
2.2 排查实战
2.2.1 使用top找到cpu飙高的java进程ID
首先我们需要找到java进程ID,使用top指令:
#top
我们一看就看到了pid为14203的java的CPU使用率是99.7%并且持续飙高,那么这个肯定是代码写的有问题了。
2.2.2 使用top -p 显示进程情况
我们使用如下命名查看java进程的情况:
#top -p 14203
通过-p的方法就是只显示了指定进程的信息。
2.2.3 按H查看线程的CPU情况
在上面的界面中使用shirt+H进行查看各个线程的CPU情况:
注意这里的PID实际对应的是线程的十进制的tid,通过上面我们可以看到CPU使用很高的线程ID是14204。
2.2.4 线程十进制转换为十六进制
我们将获取到的线程十进制的转换为十六进制:
14204(十进制) = 377C(十六进制)
怎么转你不知道嘛?
方式一:找个转换网站
https://tool.oschina.net/hexconvert/
https://tool.lu/hexconvert/(这个强大,可以一下子转换出来好几个进制的)
方式二:Linux/Mac的printf
使用Linux/Mac的printf即可:
printf %x 14204 && echo
方式三:Linux/Mac的echo
Echo也是很强大的:
echo 'ibase=10;obase=16;14204'|bc
方式四:python的hex
利用python的转换hex将十进制转换为十六进制:
悟纤:师傅,你这是要飘了,要跑题了。
师傅:哈哈,师傅这是已经超神了。
2.2.5 执行jstack得到线程的堆栈信息
执行 jstack | grep -A 10 ,得到线程堆栈信息:
#jstack 14203 | grep -A 10 377c
注意:小c、小c、小c,重要的事情说3遍。
2.2.6查看对应的堆栈信息问题排查
我上面的堆栈信息可以看出出现问题的类是EndlessLoopTest.java,代码行号是13:
源码:
package com.kfit.jvm;import java.net.Socket;/** * 死循环测试 */public class EndlessLoopTest { public void test(){ int random = (int) (java.lang.Math.random() * 1000); while (random < 100) { random = random * 10; } System.out.println(random); } public static void main(String[] args) { EndlessLoopTest test = new EndlessLoopTest(); for(int i=0;i<5000;i++){ test.test(); } }}
我们分析这个while循环,看着挺正常的,但是如果当random为0的时候,不就是陷入死循环了吗。
悟纤小结
悟纤:师傅,你真是我的偶像,讲解的如此之详细,我要是再不懂,看来只能退出编程界了。
师傅:徒儿,言重了。虽然为师已经介绍的很详细了,但是难免在实际使用的时候会踩到一些坑。
悟纤:那为了避免大家采坑,我和大家总结下。
核心就是两大步骤:
(1)通过top找到线程id。
通过Linux系统的top命名找到cpu飙高的进程id;通过top -p 找到该进程id的cpu信息;然后配合shirt+H命名,就可以找到CPU线程高的线程ID:通过工具类将十进制的线程id转换为十六进制的。
(2)通过jstack找到线程的堆栈信息。
通过jstack | grep -A 10 就可以找到线程的堆栈信息。
通过top找到线程id,通过jstack找到线程的堆栈信息。
我就是我,是颜色不一样的烟火。
我就是我,是与众不同的小苹果。
à悟空学院:https://t.cn/Rg3fKJD
学院中有Spring Boot相关的课程!点击「阅读原文」进行查看!
SpringBoot视频:http://t.cn/A6ZagYTi
Spring Cloud视频:http://t.cn/A6ZagxSR
SpringBoot Shiro视频:http://t.cn/A6Zag7IV
SpringBoot交流平台:https://t.cn/R3QDhU0
SpringData和JPA视频:http://t.cn/A6Zad1OH
SpringSecurity5.0视频:http://t.cn/A6ZadMBe
Sharding-JDBC分库分表实战:
http://t.cn/A6ZarrqS
分布式事务解决方案「手写代码」:
http://t.cn/A6ZaBnIr
深入理解JVM内存模型/调优实战:
http://t.cn/A6wWMVqG