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 优化与安全加固,进一步打造稳定、高性能的生产环境。

相关推荐
Fcy6487 小时前
Linux下 进程(一)(冯诺依曼体系、操作系统、进程基本概念与基本操作)
linux·运维·服务器·进程
袁袁袁袁满7 小时前
Linux怎么查看最新下载的文件
linux·运维·服务器
代码游侠8 小时前
学习笔记——设备树基础
linux·运维·开发语言·单片机·算法
Harvey9038 小时前
通过 Helm 部署 Nginx 应用的完整标准化步骤
linux·运维·nginx·k8s
珠海西格电力科技9 小时前
微电网能量平衡理论的实现条件在不同场景下有哪些差异?
运维·服务器·网络·人工智能·云计算·智慧城市
释怀不想释怀9 小时前
Linux环境变量
linux·运维·服务器
zzzsde9 小时前
【Linux】进程(4):进程优先级&&调度队列
linux·运维·服务器
怜渠客10 小时前
彻底告别 FireFox 浏览器
firefox
聆风吟º11 小时前
CANN开源项目实战指南:使用oam-tools构建自动化故障诊断与运维可观测性体系
运维·开源·自动化·cann
NPE~11 小时前
自动化工具Drissonpage 保姆级教程(含xpath语法)
运维·后端·爬虫·自动化·网络爬虫·xpath·浏览器自动化