Administrator
发布于 2018-02-19 / 485 阅读
4

Linux 上部署和排查 Java 应用的常用命令清单

第一次登生产服务器,我连日志在哪都不知道

入职第二周,导师丢给我一台测试机的账号,说"你自己把服务起起来"。我 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 那个参数救过我好几次,只看报错那一行基本没用,异常堆栈都在下面。

还有个技巧,跟踪日志时用 lesstail -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 定位代码行。我刚上手时把这套流程抄在便签上贴显示器边框,用到第三四次就记熟了。

参考