在项目中遇到一个问题,我们服务提供给外部的一个接口 queryXXX 一直返回 429 错误(Too Many Requests),接口没有返回值,而且服务越用越卡,要重启一下才能恢复。于是马上就想到是不是因为这个接口产生了死循环,导致接口无法正确返回,同时导致后台 CPU 和内存占用飙升,顺着这个思路定位下去,确实顺利的找到的问题所在。
free -m/free -h
查看服务的后台 CPU 和内存占用,发现服务占用的内存和 CPU 过高。jps/ps/top
命令找到 CPU 和内存占用高的进程 ID(32033)。top -H -p 32033
,找到 %CPU 和 %MEM 占用高的 PID(60958),转换成16进制 ee1e。jstack -l 32033 > stack.txt
,打印调用栈信息。nid=0xee1e
(注意第三步的是十进制,日志里的是十六进制)对应接口服务,分析调用栈,发现 queryXXX 接口的状态一直是 RUNNING 状态,卡死。JStack 是 java 自带的工具,在 jdk\bin\jstack.exe 位置。以下是 Windows 的示范,在 Linux 系统上功能更多。
PS C:\Program Files\Java\jdk-11.0.2\bin> .\jstack
Usage:
jstack [-l][-e] <pid>
(to connect to running process)
Options:
-l long listing. Prints additional information about locks
-e extended listing. Prints additional information about threads
-? -h --help -help to print this help message
一般常用的是以下的命令:
jstack -l [PID]
jstack -F [PID]
-l
选项会打印额外的信息,比如说锁信息。-F
参数来强制执行 thread dump。接下来我们就可以分析打印的堆栈信息进行分析,比如我上面列举的那个问题:
Section | Example | 解释 |
---|---|---|
线程名字 | main 和 Reference Handler | 可读的线程名字,这个名字可以通过 Thread 方法 setName 设定 |
线程 ID | #1 | 每一个 Thread 对象的唯一 ID,这个 ID 是自动生成的,从 1 开始,通过 getId 方法获得 |
是否守护线程 | daemon | 这个标签用来标记线程是否是守护线程,如果是会有标记,如果不是这没有 |
优先级 | prio=10 | Java 线程的优先级,可以通过 setPriority 方法设置 |
OS 线程的优先级 | os_prio | |
CPU 时间 | cpu=94.43ms | 线程获得 CPU 的时间 |
elapsed | elapsed=509136.51s | 线程启动后经过的 wall clock time |
Address | tid | Java 线程的地址,这个地址表示的是 JNI native Thread Object 的指针地址 |
OS 线程 ID | nid | The unique ID of the OS thread to which the Java Thread is mapped |
线程状态 | Running | 线程当前状态,线程状态下面就是线程的堆栈信息 |
线程的运行状态:
此文开头解决的问题,由于是公司项目,不方便贴上定位的过程日志和代码,所以就先记录下定位的思路和基本概念。