Tomcat 企业级运维实战系列(四):Tomcat 企业级监控

Tomcat 企业级运维实战系列(四):Tomcat 企业级监控


🚀 Tomcat 系列文章导航

本系列系统讲解 Linux 环境下 Apache Tomcat 的部署、配置、管理与优化,并最终带你完成 企业级前后端分离项目上线。无论你是初学者还是想进阶的运维人员,这份路线图都能帮你快速构建完整的知识体系。
⚠️ 该系列所有涉及的软件包和项目都可以私信博主免费获取


一:监控工具

1)概述

  • 可通过 Zabbix / Grafana / Prometheus 等工具监控 Tomcat/Java。

  • 需要在 Tomcat 中开启 Java 远程监控功能(JMX Remote)

  • 也可在 Windows 安装 JDK,使用 jconsole / jvisualvm 工具远程连接 Tomcat 进行监控。

2)流程

  1. 在 Linux 修改 Tomcat 启动参数,开启 JMX 远程监控功能。

  2. 重启 Tomcat 使配置生效。

  3. 查看进程确认参数是否加载。

  4. 在 Windows 安装 JDK,并使用工具连接。

3)部署

  1. 修改 Tomcat 启动参数

    参数 说明
    -Dcom.sun.management.jmxremote 开启远程监控功能
    -Dcom.sun.management.jmxremote.port=12345 指定 JMX 端口
    -Dcom.sun.management.jmxremote.authenticate=false 关闭认证
    -Dcom.sun.management.jmxremote.ssl=false 关闭 SSL 加密
    -Djava.rmi.server.hostname=192.168.2.104 绑定监听的 IP(通常填内网IP)
    bash 复制代码
    [root@web01 ~]# vim /opt/module/tomcat-8.5.87/bin/catalina.sh

    ⚠️ 注意:Tomcat 8.5+ 中配置需用 \ 换行。

    bash 复制代码
    CATALINA_OPTS="$CATALINA_OPTS \
    -Dcom.sun.management.jmxremote \
    -Dcom.sun.management.jmxremote.port=12345 \
    -Dcom.sun.management.jmxremote.authenticate=false \
    -Dcom.sun.management.jmxremote.ssl=false \
    -Djava.rmi.server.hostname=192.168.2.104"
  2. 启动 Tomcat

    bash 复制代码
    [root@web01 ~]# systemctl start tomcat
  3. Window 安装 JDK

    配置环境变量:

    • JAVA_HOME=D:\...\jdk1.8.0_212
    • Path=%JAVA_HOME%\bin;
    bash 复制代码
    C:\Users\86152>java -version
    java version "1.8.0_212"
    Java(TM) SE Runtime Environment (build 1.8.0_212-b10)
    Java HotSpot(TM) 64-Bit Server VM (build 25.212-b10, mixed mode)
  4. 监控

    • 使用 JConsole 监控

      路径:D:\...\jdk1.8.0_212\bin\jconsole.exe

      bash 复制代码
      1. 打开 JConsole
      
      2. 选择 远程连接 → 输入 IP:指定 JMX 端口
      
      3. 如果关闭认证/SSL,则为"不安全连接"
      
      4. 进入控制台即可查看 JVM 的内存、线程、类加载等信息
    • 使用 JVisualVM 监控

      路径:D:\...\jdk1.8.0_212\bin\jvisualvm.exe

      bash 复制代码
      1. 打开 JVisualVM
      
      2. 添加远程主机 → 输入主机名/IP(不用带端口)
      
      3. 添加 JMX 连接 → 输入 IP:指定 JMX 端口
      
      4. 双击连接,即可查看监控效果



二:监控命令

1)jps

  • 作用 :查看正在运行的 Java 进程(类似 ps -ef | grep java,但只显示 Java 进程)。

  • 常用命令

    bash 复制代码
    # 查看所有 Java 进程
    jps
    
    # 查看详细信息(主类名、JVM参数等)
    jps -lvm | grep tomcat

2)jstack

  • 作用 :查看指定 Java 进程的线程信息,用于分析线程状态、死锁等问题。

  • 操作步骤

    线程状态说明:

    1. NEW:新建
    2. RUNNABLE:就绪
    3. RUNNING:运行中
    4. BLOCKED:阻塞(常见于 IO)
    5. DEAD:死亡
    bash 复制代码
    # 查看 Java 进程 PID
    jps
    80177 Bootstrap
    
    # 查看该进程的线程栈信息
    jstack 80177
    
    # 仅查看线程状态
    jstack 80177 | grep -i state

3)jmap

  • 作用 :查看或导出 JVM 内存信息

  • 常用命令

    bash 复制代码
    # 查看 JVM 内存使用情况
    jmap -heap 80177
    
    # 导出 JVM 内存镜像文件(用于分析)
    jmap -dump:format=b,file=8080.hprof 80177

4)MAT 工具分析

  • 作用图形化分析 .hprof 堆内存镜像文件,排查内存泄漏、对象占用过大等问题。

  • 使用步骤

    1. 在 Linux 导出堆文件:

      bash 复制代码
      jmap -dump:format=b,file=8080.hprof 80177
      
      sz 8080.hprof   # 传输到本地
    2. Windows/Mac/Linux 安装 MAT

      下载地址: Memory Analyzer (MAT) | The Eclipse Foundation


    3. 打开 .hprof 文件

      bash 复制代码
      1. 欢迎页选择 Open a Heap Dump
      
      2. 选择 Leak Suspects Report 查看内存泄漏报告

三:监控脚本

show-busy-java-thread.sh 显示当前环境中,所有繁忙的java线程. 以百分数显示使用率最高的前几个线程.

bash 复制代码
#!/bin/bash
# @Function
# Find out the highest cpu consumed threads of java, and print the stack of these threads.
#
# @Usage
#   $ ./show-busy-java-threads.sh
#
# @author Jerry Lee

readonly PROG=`basename $0`
readonly -a COMMAND_LINE=("$0" "$@")

usage() {
    cat <<EOF
Usage: ${PROG} [OPTION]...
Find out the highest cpu consumed threads of java, and print the stack of these threads.
Example: ${PROG} -c 10

Options:
    -p, --pid       find out the highest cpu consumed threads from the specifed java process,
                    default from all java process.
    -c, --count     set the thread count to show, default is 5
    -h, --help      display this help and exit
EOF
    exit $1
}

readonly ARGS=`getopt -n "$PROG" -a -o c:p:h -l count:,pid:,help -- "$@"`
[ $? -ne 0 ] && usage 1
eval set -- "${ARGS}"

while true; do
    case "$1" in
    -c|--count)
        count="$2"
        shift 2
        ;;
    -p|--pid)
        pid="$2"
        shift 2
        ;;
    -h|--help)
        usage
        ;;
    --)
        shift
        break
        ;;
    esac
done
count=${count:-5}

redEcho() {
    [ -c /dev/stdout ] && {
        # if stdout is console, turn on color output.
        echo -ne "\033[1;31m"
        echo -n "$@"
        echo -e "\033[0m"
    } || echo "$@"
}

yellowEcho() {
    [ -c /dev/stdout ] && {
        # if stdout is console, turn on color output.
        echo -ne "\033[1;33m"
        echo -n "$@"
        echo -e "\033[0m"
    } || echo "$@"
}

blueEcho() {
    [ -c /dev/stdout ] && {
        # if stdout is console, turn on color output.
        echo -ne "\033[1;36m"
        echo -n "$@"
        echo -e "\033[0m"
    } || echo "$@"
}

# Check the existence of jstack command!
if ! which jstack &> /dev/null; then
    [ -z "$JAVA_HOME" ] && {
        redEcho "Error: jstack not found on PATH!"
        exit 1
    }
    ! [ -f "$JAVA_HOME/bin/jstack" ] && {
        redEcho "Error: jstack not found on PATH and $JAVA_HOME/bin/jstack file does NOT exists!"
        exit 1
    }
    ! [ -x "$JAVA_HOME/bin/jstack" ] && {
        redEcho "Error: jstack not found on PATH and $JAVA_HOME/bin/jstack is NOT executalbe!"
        exit 1
    }
    export PATH="$JAVA_HOME/bin:$PATH"
fi

readonly uuid=`date +%s`_${RANDOM}_$$

cleanupWhenExit() {
    rm /tmp/${uuid}_* &> /dev/null
}
trap "cleanupWhenExit" EXIT

printStackOfThread() {
    local line
    local count=1
    while IFS=" " read -a line ; do
        local pid=${line[0]}
        local threadId=${line[1]}
        local threadId0x=`printf %x ${threadId}`
        local user=${line[2]}
        local pcpu=${line[4]}

        local jstackFile=/tmp/${uuid}_${pid}

        [ ! -f "${jstackFile}" ] && {
            {
                if [ "${user}" == "${USER}" ]; then
                    jstack ${pid} > ${jstackFile}
                else
                    if [ $UID == 0 ]; then
                        sudo -u ${user} jstack ${pid} > ${jstackFile}
                    else
                        redEcho "[$((count++))] Fail to jstack Busy(${pcpu}%) thread(${threadId}/0x${threadId0x}) stack of java process(${pid}) under user(${user})."
                        redEcho "User of java process($user) is not current user($USER), need sudo to run again:"
                        yellowEcho "    sudo ${COMMAND_LINE[@]}"
                        echo
                        continue
                    fi
                fi
            } || {
                redEcho "[$((count++))] Fail to jstack Busy(${pcpu}%) thread(${threadId}/0x${threadId0x}) stack of java process(${pid}) under user(${user})."
                echo
                rm ${jstackFile}
                continue
            }
        }
        blueEcho "[$((count++))] Busy(${pcpu}%) thread(${threadId}/0x${threadId0x}) stack of java process(${pid}) under user(${user}):"
        sed "/nid=0x${threadId0x} /,/^$/p" -n ${jstackFile}
    done
}


ps -Leo pid,lwp,user,comm,pcpu --no-headers | {
    [ -z "${pid}" ] &&
    awk '$4=="java"{print $0}' ||
    awk -v "pid=${pid}" '$1==pid,$4=="java"{print $0}'
} | sort -k5 -r -n | head --lines "${count}" | printStackOfThread

四:Java 系统负载与性能排查流程

1)初步观察系统负载

  • 使用 w / top / uptime 查看系统整体负载

  • 如果 负载 > CPU 核心数 × 60%~70%,说明系统存在瓶颈

  • 命令:

    复制代码
    w
    uptime
    lscpu
    top -1

2)判断负载来源

  • CPU 负载

    • 命令:top → 观察 %us(用户态 CPU 占比)
    • 若 CPU 过高,说明是计算型压力
    • 工具:ps aux / htop / top
  • IO 负载

    • 命令:top → 观察 %wa(I/O 等待占比)
    • 若 IO 过高,说明磁盘或网络瓶颈
    • 工具:iotop

3)确定问题进程

  • 使用 ps aux / htop / top 查找占用 CPU/内存较高的进程
  • 获取 Java 进程 ID (PID)

4)进一步分析

  1. 查看日志

    • 检查应用日志(如 catalina.out),排查异常或错误
  2. 线程分析

    • 使用 jstack 查看线程堆栈信息

    • 关注线程状态(Runnable、Blocked、Waiting 等)

    • 示例:

      bash 复制代码
      jstack <pid>
      jstack <pid> | grep -i state
  3. JVM 内存分析

    • 导出堆内存镜像:

      bash 复制代码
      jmap -dump:format=b,file=/root/jvm.hprof <pid>
    • 查看 JVM 内存使用情况:

      bash 复制代码
      jmap -heap <pid>
  4. 堆文件分析

    • .hprof 文件传至本地
    • 使用 MemoryAnalyzer Tool (MAT)Eclipse MAT 插件打开
    • 重点查看:
      • Leak Suspects Report(内存泄漏嫌疑报告)
      • 对象占用情况(哪些类实例过多/占用大)

5)问题解决与总结

  1. 根据 jstack 定位线程死锁/阻塞点
  2. 根据 jmap + MAT 分析内存泄漏问题
  3. 结合日志和监控,优化代码或调整 JVM 参数
  4. 验证问题是否解决

总结

📌 本节重点回顾

  • 监控工具

    • 学会了通过 JMX 远程监控,结合 JConsole、JVisualVM 等工具查看 JVM 内存、线程、类加载等运行状态。

    • 掌握了在 Linux 修改 catalina.sh 启动参数 的方法,开启远程监控端口。

  • 监控命令

    • jps → 查看 Java 进程

    • jstack → 分析线程堆栈,定位死锁/阻塞

    • jmap → 导出内存快照,分析堆使用情况

    • 配合 MAT 工具 图形化分析 .hprof,排查内存泄漏

  • 监控脚本

  • 性能排查流程

    • 先看系统整体负载(w、top、uptime)

    • 判断是 CPU 负载 还是 IO 负载

    • 确定问题进程 → 结合 jstack / jmap 分析

    • 使用 日志 + 堆分析工具(MAT) 找出瓶颈

    • 优化代码、JVM 参数或系统资源配置

至此,你已经掌握了 Tomcat 与 JVM 的监控手段,能从 进程 → 线程 → 内存 → 系统层面 全方位排查性能问题。

下一篇将进入 Tomcat 优化与安全加固,进一步打造稳定、高性能的生产环境。

相关推荐
礼拜天没时间.5 小时前
Tomcat 企业级运维实战系列(一):核心概念与基础部署
java·运维·centos·tomcat
大白菜201501056 小时前
ubuntu 创建系统服务 开机自启
linux·运维·ubuntu
当归10246 小时前
Ruoyi项目MyBatis升级MyBatis-Plus指南
java·tomcat·mybatis
willhuo6 小时前
学生请假就餐系统
运维·服务器·.netcore
lhxsir6 小时前
linux连接服务器sftp无法输入中文
linux·运维·服务器
小沈熬夜秃头中୧⍤⃝7 小时前
无需服务器也能建网站:Docsify+cpolar让技术文档分享像写笔记一样简单
运维·服务器·笔记
wanhengidc7 小时前
云端虚拟云手机该如何进行使用?
运维·科技·安全·游戏·智能手机
过期动态8 小时前
MySQL内置的各种单行函数
java·数据库·spring boot·mysql·spring cloud·tomcat
key_Go8 小时前
07.《交换机三层功能、单臂路由与端口安全基础知识》
运维·服务器·网络·华为·智能路由器·交换机