第一次登生产服务器,我连日志在哪都不知道
入职第二周,导师丢给我一台测试机的账号,说"你自己把服务起起来"。我 cd 了半天,连 jar 包放哪都找不到,最后是靠 ls 一个个目录翻出来的。那天之后我把常用的命令整理成了这份清单,现在贴出来,也给刚上手的同学参考。
环境是 CentOS 7 + JDK 8,下面的命令都是我实际用过的。
找 Java 进程
# 列出所有 Java 进程的 pid 和主类,最常用的第一条命令
jps -l
# 输出:28321 order-service.jar
# 带 JVM 参数的版本,看堆大小、GC 配置很方便
jps -lv
# 如果 jps 报 "command not found",说明只装了 JRE,或者用 ps 顶上
ps -ef | grep java | grep -v grep
grep -v grep 那一段是去掉 grep 自己那条,不然每次都多一行干扰。
看机器整体状态:top
top
进 top 之后我一般会按这几个键:
- 1:展开每个 CPU 核的使用率。只看一个总的 us 值容易误判,我遇到过 4 核机器总 CPU 25% 但实际上是单核跑满了。
- shift + m:按内存排序,Java 进程通常就在第一个。
- shift + h:切到线程视图,能看到哪个 tid 吃 CPU。
top 第一行 load average: 0.15, 0.08, 0.05 这三个是 1/5/15 分钟的平均负载。4 核的机器,load 长期超过 4 就要警惕了。注意 load 高不等于 CPU 高,大量线程卡在 IO 上(比如数据库慢查询)也会把 load 顶上去。
找到了高 CPU 的线程,还需要把它和 Java 线程对上:
top -Hp 28321 # 看这个进程里哪个线程吃 CPU,假设是 28345
printf '%x\n' 28345 # 10 进制转 16 进制:6eb9
jstack 28321 | grep -A 20 'nid=0x6eb9'
这套组合拳是我从师傅那学的,定位死循环特别好使。
内存和磁盘
free -m # 看内存,注意 -m 是 MB
df -h # 磁盘剩余空间
du -sh /data/logs/* # 某个目录下各文件占多大
free -m 里有个坑:Linux 会把空闲内存拿去做磁盘缓存(buff/cache),所以 free 那一列看着很小是正常的,真正可用的是 available 那一列。我第一次看到 free 只剩 200MB 差点以为要 OOM 了。
磁盘满是最常见的故障之一。我们线上出过一次,日志把 /data 写满了,服务直接开始报 java.io.IOException: No space left on device。排查就是 df -h 然后 du -sh 一层层找,最后发现是某个 DEBUG 日志忘了关,一天写了 30 多个 G。
网络和端口
netstat -tunlp | grep 8080 # 谁占用了 8080
netstat -an | grep ESTABLISHED | wc -l # 当前established连接数
lsof -i:8080 # 同上,有些机器没装 netstat
netstat -tunlp 这几个参数拆开记:t 是 tcp,u 是 udp,n 是不反解域名(快很多),l 是监听中,p 是显示进程。我一般直接 netstat -tunlp | grep java 看服务监听了哪些端口。
看日志
tail -f app.log # 实时跟踪
tail -n 200 app.log # 看最后 200 行
grep 'ERROR' app.log # 过滤错误
grep -C 10 'NullPointer' app.log # 打印匹配行前后各 10 行,看上下文
grep -A 30 '2018-02-19 14:2' app.log # 看某个时间点之后 30 行
-C 那个参数救过我好几次,只看报错那一行基本没用,异常堆栈都在下面。
还有个技巧,跟踪日志时用 less 比 tail -f 灵活:
less app.log # 进去后按 shift+F 进入 follow 模式,ctrl+C 退出跟踪
# 然后可以用 /关键字 搜索,n 下一个
启动与守护
最开始的写法,关掉终端服务就没了:
java -jar order-service.jar # 别这么干
加上 nohup 和 &:
nohup java -Xms512m -Xmx512m -XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/data/dump/ \
-jar order-service.jar > /data/logs/order-service.log 2>&1 &
几个参数说明:
-Xms和-Xmx设成一样,避免堆动态扩容带来的抖动。这个是我们组的规范。-XX:+HeapDumpOnOutOfMemoryError一定要加。OOM 时自动存快照,不然只能重启了事,事后啥也查不到。存下来的 hprof 文件通常好几个 G,注意磁盘。2>&1是把 stderr 合并到 stdout,不然异常堆栈会丢。
重启脚本我一般是这么写的:
#!/bin/bash
PID=$(ps -ef | grep 'order-service.jar' | grep -v grep | awk '{print $2}')
if [ -n "$PID" ]; then
kill -15 $PID # 先优雅停机,给 Spring 收尾的时间
sleep 5
kill -9 $PID 2>/dev/null # 还没退出才强杀
fi
用 kill -15 而不是直接 -9,是因为 Spring Boot 2.0 会注册 shutdown hook,收到 TERM 信号会优雅关闭内嵌 Tomcat、释放连接池。-9 是强杀,正在处理的请求直接断掉。
小结
再补三个 JDK 自带的工具
jstat -gcutil 28321 1000 10 # 每秒打印一次 GC 统计,共 10 次
输出里 O 列是老年代占用百分比,YGC/FGC 是 GC 次数,FGCT 是 Full GC 累计耗时。看到 FGC 在十几秒内涨了好几次,基本可以断定内存有问题。这比去翻 gc.log 快。
jmap -heap 28321 # 看堆配置和使用情况
jmap -histo 28321 | head -20 # 看占用内存最多的类
jmap -dump:format=b,file=/data/dump.hprof 28321 # 导出堆快照
jmap -histo 是我最常用的,一眼就能看出是哪个类的对象撑爆了内存。之前一次 OOM 就是靠它定位到某个 List 里塞了 800 万个对象。注意 jmap -dump 会触发一次 Full GC 并且暂停应用,线上导快照前最好先把这台机器从负载均衡摘掉。
jinfo -flags 28321 # 查看这个 JVM 实际生效的参数
这个用来确认"我配的参数到底生效了没有"。我有次改了 JVM 参数重启后没生效,就是靠它发现启动脚本里 JAVA_OPTS 被后面的赋值覆盖了。
部署目录我后来定的规范
被乱放的 jar 包坑过之后,我们组统一了目录结构:
/data/app/order-service/
├── order-service.jar # 当前版本
├── order-service.jar.bak # 上一个版本,回滚用
├── start.sh
├── stop.sh
└── logs/
├── app.log
└── gc.log
回滚的时候 mv 一下就行,不用重新打包。代价是每次上线要多花几秒钟做备份,但比起回滚时找不到上一版 jar 的慌张,这点成本不值一提。
小结
这些命令单独看都很简单,关键是能串起来用:top 找高 CPU 线程 → printf 转 16 进制 → jstack 定位代码行。我刚上手时把这套流程抄在便签上贴显示器边框,用到第三四次就记熟了。