所在目录
- 获取PID;通过 ps 或 top 命令获取到进程的PID。(以下记为 12345)
$ ps aux |grep java
# ps aux与ps -ef 目的都是显示所有在运行的进程, aux是用bsd的格式输出,-ef是用标准格式输出。由于ps版本的原因,也支持ps -aux;
# ps aux最初用到Unix Style中,而ps -ef被用在System V Style中;
# -e 代表列出所有进程,-f 代表完整的格式,-l 代表长格式,有时候也用 -F 代表超完整的格式。
- ps
- aux: bsd 格式输出, 被用到 Unix Style 中 (由于一些ps版本的原因,也支持
ps -aux
) - ef: 标准格式输出, 被用在 System V Style 中
- aux: bsd 格式输出, 被用到 Unix Style 中 (由于一些ps版本的原因,也支持
- 通过 proc 查看对应目录, 并接上上面查到的 PID 值
#使用命令 ls -l /proc/PID
$ ls -l /proc/12345
Linux在启动一个进程时,系统会在/proc下创建一个以PID命名的文件夹。
在该文件夹下会有我们的进程的信息
其中:
cwd文件,符号链接的是进程运行目录;
exe文件,符号连接的是执行程序的绝对路径;
cmdline文件就是程序运行时输入的命令行命令;
environ文件记录了进程运行时的环境变量;
fd目录下是进程打开或使用的文件的符号连接;
查看 java 程序运行时的端口
# 显示java程序运行的端口;
netstat -nap | grep java
排查java程序CPU占用过高问题
top
按下 p, 记下 pid (以下pid 均为 12345)- 显示线程列表:
ps -mp 12345 -o THREAD, tid, time
# 记下tid 为 23456 - 将要选择的线程ID转换为16进制格式:
printf “%x\n” 23456
# 记为 660a - 最后打印线程的堆栈信息:
jstack 12345 |grep 660a -A 30
# grep -A num, --after-context=num
- 另一种
- top 记下 pid
ps H -eo pid,tid,%cpu |grep [pid]
# H 打印进程树 -eo 感兴趣的内容- 或者
top Hp [pid]
jstack [pid]
然后讲上面第2步找到的tid 换算为十六进制(原来为10进制), 在打印的信息中找到对应的 nid (nid is Native Thread ID). 得到堆栈.
案例
日志写入阻塞的问题
Runtime.getRuntime().exec("java -jar xxx.jar");
原因: 服务无法写入日志, 所以接口阻塞了
通常要获取 exec() 执行的日志, 通常是读取它的 OutputStream 流, 且输出流缓冲区大小有限制, 写满后不会自动覆盖, 而是阻塞
分析 Runtime.getRuntime().exec()
的源码, 调用了 ProcessBuilder
的 start()
方法, 最后调用 new ProcessImpl()
, 该类与操作系统相关
最终调用 java.lang.ProcessImpl#create
方法, 这是一个 native
方法, 继续往下:
#define PIPE_SIZE (4096+24)
//......
if (!CreatePipe(
&pHolder->pipe[OFFSET_READ],
&pHolder->pipe[OFFSET_WRITE],
NULL, /* we would like to inherit
default process access,
instead of 'Everybody' access */
PIPE_SIZE))
CreatePipe 是操作系统的 api: Creates an anonymous pipe, and returns handles to the read and write ends of the pipe.
When a process uses WriteFile to write to an anonymous pipe, the write operation is not completed until all bytes are written. If the pipe buffer is full before all bytes are written, WriteFile does not return until another process or thread uses ReadFile to make more buffer space available.
If the pipe buffer is full before all bytes are written, WriteFile does not return. 如果管道被写满 (4096+24) 字节, 那么写会被阻塞, 直到有空间为止;
所以, 打印日志的线程都被阻塞了, 因为代码根本没有读取 pipe
修改方法
- 要么读取
pipe
的输出, 要么一开始就不写console
如何不写 console
: 命令后边加上 > /dev/null
- 尽量使用文件日志等方式, 不要使用
ConsoleAppender
可以参考 Logback 的 Jira 问题中的评论: https://jira.qos.ch/browse/LOGBACK-1422
Writing to disk takes time. writing to the console takes even more time. Do not use ConsoleAppender in production.
基本排查命令
top
查看整个系统资源使用情况, 可以查看实时的CPU使用情况, 也可以查看最近一段时间的CPU使用情况;
P = CPU ⬇️ ; T = TIME ⬆️ ; M = MEM ⬆️ps
强大的进程状态监控命令。可以查看进程以及进程中线程的当前CPU使用情况。属于当前状态的采样数据。jstack
Java提供的命令。可以查看某个进程的当前线程栈运行情况。根据这个命令的输出可以定位某个进程的所有线程的当前运行状态、运行代码,以及是否死锁等等。pstack
可以查看某个进程的当前线程栈运行情况。free -m
查看内存使用情况;(m代表MB)iostat
查看磁盘读写活动情况;netstat
查看网络连接情况;df -h
查看磁盘空间使用情况;du -sh
查看文件大小情况;