# tomcat&java异常排查思路

好的，在 Ubuntu 14.04 上排查 Tomcat 异常导致网页访问缓慢的问题，可以遵循以下步骤和命令。核心思路是：先定位是系统资源瓶颈还是应用内部问题，再深入 Java 应用内部。

\---

\### 排查思路流程图

```mermaid  
flowchart TD  
A\[Tomcat访问缓慢\] --&gt; B(系统级资源排查)  
A --&gt; C(应用级内部排查)

subgraph B\[第一步：系统资源排查\]  
 direction TB  
 B1\[CPU&lt;br&gt;top, vmstat\] --&gt; B2{资源异常？}  
 B3\[内存&lt;br&gt;free, vmstat\] --&gt; B2  
 B4\[磁盘I/O&lt;br&gt;iostat, iotop\] --&gt; B2  
 B5\[网络&lt;br&gt;netstat, ss\] --&gt; B2  
 B2 -- 是 --&gt; B6\[针对性地解决资源瓶颈\]  
end

subgraph C\[第二步：Java应用排查\]  
 direction TB  
 C1\[检查Tomcat日志&lt;br&gt;tail -f catalina.out\] --&gt; C2{发现异常错误？}  
 C2 -- 是 --&gt; C3\[根据错误日志解决\]  
 C2 -- 否 --&gt; C4\[检查线程状态&lt;br&gt;jstack\]  
 C4 --&gt; C5\[分析线程栈&lt;br&gt;查找阻塞、死锁、等待\]  
 C5 --&gt; C6\[发现可疑线程？\]  
 C6 -- 是 --&gt; C7\[结合代码分析问题\]  
end

B2 -- 否 --&gt; C  
C6 -- 否 --&gt; D\[第三步：性能分析\]  
D --&gt; D1\[生成Heap Dump&lt;br&gt;jmap\]  
D --&gt; D2\[分析内存使用&lt;br&gt;MAT, jhat\]  
D --&gt; D3\[找出内存泄漏或过度消耗\]  
```

\---

\### 第一步：系统级资源排查 (快速确认瓶颈)

首先排除服务器本身的资源瓶颈（CPU、内存、磁盘I/O）。

1\. \*\*综合性能查看 (`top` / `htop`)\*\*  
 ```bash  
 top  
 ```  
 \* \*\*看第一行 (load average):\*\* 1分钟、5分钟、15分钟的系统平均负载。如果值\*\*超过CPU核心数\*\*（例如4核机器负载长期&gt;4），说明系统负载过高。  
 \* \*\*看 %CPU:\*\* 检查 `%Cpu(s)` 这一行的 `%id`（空闲）是否很低，`%wa`（I/O等待）是否很高。高 `%wa` 表示磁盘是瓶颈。  
 \* \*\*看 %MEM:\*\* 向下看进程列表，按 `Shift + M` 按内存使用排序，找到 `java` 或 `tomcat` 进程，看其内存使用率是否异常高，接近分配的最大值。

2\. \*\*内存和Swap使用情况 (`free`)\*\*  
 ```bash  
 free -h  
 ```  
 \* 重点关注 `available` 列（Ubuntu 14.04 可能没有，就看 `free` 列）。如果 `free` 内存极少，并且 `swap` 的 `used` 在不断增加，说明物理内存不足，系统在使用交换分区，这会极大拖慢速度。

3\. \*\*磁盘I/O状态 (`iostat`, `iotop`)\*\*  
 ```bash  
 # 需要安装 sysstat 包: sudo apt-get install sysstat  
 iostat -xm 2

 # 需要安装 iotop 并需要root权限: sudo apt-get install iotop &amp;&amp; sudo iotop  
 ```  
 \* 查看 `%util` 列，如果持续接近100%，表示磁盘I/O非常繁忙。  
 \* 查看 `await` 列，表示I/O操作的平均等待时间（毫秒），如果很高（例如&gt;100ms），说明磁盘响应慢。

4\. \*\*网络连接检查 (`netstat`, `ss`)\*\*  
 ```bash  
 # 查看Tomcat连接数（ESTABLISHED状态）  
 netstat -natp | grep :8080 | grep ESTABLISHED | wc -l

 # 或者使用更快的 ss 命令  
 ss -t sport = :8080 state established | wc -l  
 ```  
 \* 连接数过多可能耗尽线程池资源。

\---

\### 第二步：Java应用内部排查 (核心步骤)

如果系统资源正常，问题很可能出在应用本身：GC、死锁、阻塞、内存泄漏等。

1\. \*\*查看Tomcat日志\*\*  
 ```bash  
 # 进入Tomcat的logs目录，查看最新的catalina.out和localhost日志  
 cd /path/to/tomcat/logs  
 tail -f catalina.out  
 grep -i "exception" catalina.out  
 ```  
 \* \*\*首先检查有无明显的异常堆栈信息\*\*，如 `OutOfMemoryError`, `NullPointerException`, 数据库连接超时等。

2\. \*\*查找Java进程PID\*\*  
 ```bash  
 # 通常Tomcat的进程名是java  
 ps -ef | grep java  
 # 或  
 jps -l  
 ```  
 \* 找到对应的Tomcat进程的PID（例如 `12345`）。

3\. \*\*检查GC情况 (`jstat`) - 非常有用\*\*  
 ```bash  
 # 需要JDK工具，假设PID为12345  
 jstat -gcutil 12345 2s  
 ```  
 \* \*\*输出列解读:\*\* `S0`, `S1` (幸存区使用率), `E` (伊甸园区使用率), `O` (老年代使用率), `M` (元空间使用率), `YGC` (年轻GC次数), `YGCT` (年轻GC时间), `FGC` (Full GC次数), `FGCT` (Full GC时间)  
 \* \*\*关键指标:\*\*  
 \* \*\*`FGC` (Full GC) 次数频繁\*\* 且 \*\*`FGCT` (Full GC时间) 很长\*\*：这是导致应用“卡顿”的最常见原因。如果 `O` (老年代) 使用率一直很高（如&gt;95%），很可能存在\*\*内存泄漏\*\*。

4\. \*\*查看线程栈 (`jstack`) - 排查死锁和阻塞\*\*  
 ```bash  
 # 生成当前线程的快照  
 jstack 12345 &gt; jstack.log

 # 或者连续抓取几次，对比分析  
 jstack 12345 &gt; jstack\_1.log  
 sleep 10  
 jstack 12345 &gt; jstack\_2.log  
 ```  
 \* \*\*分析 `jstack.log`:\*\*  
 \* 搜索 `deadlock` 看有无死锁信息。  
 \* 搜索 `blocked` 看大量线程阻塞在哪个锁上。  
 \* 看线程的\*\*状态\*\* (`RUNNABLE`, `WAITING`, `BLOCKED`) 和\*\*堆栈信息\*\*，找出大多数线程在做什么。例如，如果很多线程卡在数据库连接 `getConnection()` 上，可能是连接池不够用。

5\. \*\*生成堆转储文件 (`jmap`) - 用于分析内存泄漏\*\*  
 ```bash  
 # 生成Heap Dump文件（文件较大，注意磁盘空间）  
 jmap -dump:live,format=b,file=heap\_dump.hprof 12345  
 ```  
 \* 这个命令会对生产环境有一定影响（可能触发STW的Full GC），谨慎使用。  
 \* 生成的文件 `heap\_dump.hprof` 可以下载到本地，用 \*\*Eclipse MAT (Memory Analyzer Tool)\*\* 或 \*\*jhat\*\* 工具分析，查看是什么对象占用了大量内存。

\---

\### 第三步：Tomcat自身配置检查

1\. \*\*连接数配置\*\*  
 检查 `$CATALINA\_HOME/conf/server.xml` 中的连接器配置：  
 ```xml  
 &lt;Connector port="8080" protocol="HTTP/1.1"  
 connectionTimeout="20000"  
 maxThreads="200" &lt;!-- 最大线程数是否足够？ --&gt;  
 acceptCount="100" &lt;!-- 等待队列长度 --&gt;  
 ... /&gt;  
 ```  
 \* 如果并发请求数超过 `maxThreads`，多余的请求会在队列中等待，导致响应变慢。

\### 总结与行动指南

1\. \*\*`top`\*\* -&gt; 发现CPU或内存高。  
2\. \*\*`jstat -gcutil`\*\* -&gt; 确认是频繁Full GC导致。  
3\. \*\*`jstack`\*\* -&gt; 检查线程状态，看GC时线程在做什么，有无死锁。  
4\. \*\*`jmap` + MAT\*\* -&gt; 分析是什么对象占满了内存，定位内存泄漏的代码。  
5\. \*\*查看日志\*\* -&gt; 始终是第一步，寻找任何明显的错误线索。  
6\. \*\*检查配置\*\* -&gt; 确认 `maxThreads`、数据库连接池大小等配置是否合理。

根据上述步骤，你通常可以定位到是系统资源问题、GC问题、代码阻塞问题还是配置问题。

好的 👍 我帮你写一个 **适合 Ubuntu14.04 + Tomcat 的监控采集脚本**。  
脚本会每天定期执行，调用 `jstack` 或 `jstat` 收集 Tomcat 的 JVM 信息，并写入日志文件，每条日志前加上时间戳。

### 示例脚本 `collect_tomcat_jvm.sh`

```bash
#!/bin/bash
# 收集 Tomcat JVM 的 jstack/jstat 信息
# 保存到日志文件：/var/log/tomcat_jvm_monitor.log

# 配置项
LOG_DIR="/var/log"
LOG_FILE="$LOG_DIR/tomcat_jvm_monitor.log"
JAVA_HOME="/usr/lib/jvm/java-7-openjdk-amd64"   # 修改为你实际的 JDK 路径
TOMCAT_USER="tomcat"  # 修改为运行 tomcat 的用户
DATE=$(date +"%Y-%m-%d %H:%M:%S")

# 找到 Tomcat 的 Java 进程 PID
PID=$(ps -u $TOMCAT_USER -o pid,comm | grep java | awk '{print $1}')

if [ -z "$PID" ]; then
    echo "[$DATE] 未找到 Tomcat 进程" >> $LOG_FILE
    exit 1
fi

# 写入时间戳
echo "==================== [$DATE] ====================" >> $LOG_FILE

# 收集 jstat GC 信息
$JAVA_HOME/bin/jstat -gc $PID >> $LOG_FILE 2>&1

# 收集线程堆栈
$JAVA_HOME/bin/jstack -l $PID >> $LOG_FILE 2>&1

echo -e "\n" >> $LOG_FILE

```

---

### 使用方法

1. **保存脚本**  
    保存为 `/usr/local/bin/collect_tomcat_jvm.sh`，并赋予执行权限：
    
    ```bash
    sudo chmod +x /usr/local/bin/collect_tomcat_jvm.sh
    
    ```
2. **日志目录**  
    确认 `/var/log/` 有写权限，或者改为你自己的目录。
3. **定时任务**  
    使用 `cron` 每天定时执行，比如每天凌晨 2 点收集一次：
    
    ```bash
    crontab -e
    
    ```
    
    添加：
    
    ```bash
    0 2 * * * /usr/local/bin/collect_tomcat_jvm.sh
    
    ```
    
    如果你想更频繁（比如每 30 分钟收集一次），可以写：
    
    ```bash
    */30 * * * * /usr/local/bin/collect_tomcat_jvm.sh
    
    ```

---

👉 建议：

- `jstat` 适合收集 JVM GC/内存指标
- `jstack` 适合排查线程阻塞、死锁问题
- 日志文件会越来越大，可以结合 `logrotate` 自动切割。

---

要不要我帮你把这个脚本扩展成 **logrotate 配置**，避免日志无限增长？

好的 👍 我帮你把 `collect_tomcat_jvm.sh` 脚本对应的 **logrotate 配置文件**写出来，避免日志无限增长。

你现在的日志文件路径是：

```
/home/shuncom/tomcat_jvm_monitor.log

```

---

### logrotate 配置示例

新建文件 `/etc/logrotate.d/tomcat_jvm_monitor`：

```
/home/shuncom/tomcat_jvm_monitor.log {
    daily
    rotate 7
    compress
    delaycompress
    missingok
    notifempty
    create 0644 root root
    sharedscripts
    postrotate
        true
    endscript
}

```

以下对各行参数进行注释，但不能把行内注释写入脚本

```conf
/home/shuncom/tomcat_jvm_monitor.log {
    daily              # 每天轮转一次
    rotate 7           # 保留 7 天的日志，超过删除
    compress           # 启用压缩，老日志会变成 .gz
    delaycompress      # 延迟一天再压缩，避免影响最新日志查看
    missingok          # 如果日志不存在，不报错
    notifempty         # 如果日志为空，不进行轮转
    create 0644 root root  # 新日志的权限和属主（根据实际情况修改）
    sharedscripts
    postrotate
        # logrotate 每次切割日志后执行的操作
        # 这里不需要额外操作，因为脚本本身会继续写新日志
        true
    endscript
}

```

---

### 检查 logrotate 是否正常工作

手动执行一次（模拟日志轮转）：

```bash
sudo logrotate -f /etc/logrotate.d/tomcat_jvm_monitor

```

执行后，你应该会看到：

```
/home/shuncom/tomcat_jvm_monitor.log  →  /home/shuncom/tomcat_jvm_monitor.log.1

```

老日志会被压缩（如 `.1.gz`），新日志文件会重新创建。

---

⚠️ 注意事项：

1. 如果 `collect_tomcat_jvm.sh` 是用 `root` 运行的，`create 0644 root root` 就对了；  
    如果是 Tomcat 用户运行，改成 `create 0644 tomcat tomcat`。
2. 默认 logrotate 会每天运行一次，定时任务在 `/etc/cron.daily/logrotate`。
3. 如果日志量特别大，可以改成 `size 100M`（按大小轮转）。

---

要不要我帮你改成 **“按大小+按天混合轮转”**，比如每天轮转一次，或者日志超过 200MB 时立即切割？

👌 明白了，你要 **按天+按大小混合轮转**：

- 每天轮转一次；
- 如果日志超过 **100MB**，即使没到一天也立即切割。

logrotate 支持这种组合配置。

---

### 配置文件 `/etc/logrotate.d/tomcat_jvm_monitor`

```conf
/home/shuncom/tomcat_jvm_monitor.log {
    daily               # 每天轮转一次
    size 100M           # 日志超过 100MB 时立即切割
    rotate 7            # 保留 7 个历史文件
    compress            # 压缩历史日志
    delaycompress       # 延迟一天再压缩，避免影响最新日志查看
    missingok           # 如果日志不存在，不报错
    notifempty          # 如果日志为空，不轮转
    create 0644 root root   # 新日志文件权限和属主
    sharedscripts
    postrotate
        true
    endscript
}

```

---

### 验证

手动测试（强制执行一次）：

```bash
sudo logrotate -f /etc/logrotate.d/tomcat_jvm_monitor

```

日志会被切割为：

```
tomcat_jvm_monitor.log
tomcat_jvm_monitor.log.1
tomcat_jvm_monitor.log.2.gz
...

```

---

⚠️ 补充说明：

- `daily + size` 会实现 **二选一触发**，即每天轮转一次，或者超过 100MB 时立即切割。
- `rotate 7` 表示最多保留 7 个旧文件，超过会自动删除。
- 如果你想确保 **每天只切一次**（即使超过 100MB 也只在第二天切割），那要用 `maxsize` 而不是 `size`。

---

要不要我帮你写个对比表，解释 `size` 和 `maxsize` 的区别，方便你选择？