彻底解决你 ActiveMQ CPU 飙高、假死、连接泄露、日志撑爆磁盘问题

问题背景:activemq运行一段时间后,java进程达到300%,而且一直居高不下,这时很多消费端和生产端已经无法连接。

运行环境:

java运行版本(jdk-21_linux-x64_bin.tar.gz)

activemq版本(apache-activemq-6.0.1)

运行系统Centos7.9

硬件4核8G

你可能已经尝试过以下操作

1、禁用 JDK21 虚拟线程(根源 BUG,导致 CPU 无限飙升)
2、替换 GC 为 G1(解决 JDK21 默认 ZGC 高 CPU)
3、关闭 JMX 监控(修复 ActiveMQ 6.0.1 线程泄漏)
4、优化 CentOS7 内核参数(解决网络 IO 空转 CPU)

但问题已经并没有彻底解决

以下才是真正彻底解决问题的兜底方案,亲测有效

彻底解决方案(分4步,全部执行,一次根治)

彻底解决方案(分4步,全部执行,一次根治)

第一步:优化JVM参数(核心,解决CPU反复飙高)

之前的脚本仅配置了基础GC参数,需补充「内存溢出、线程监控、GC日志」参数,避免内存堆积和线程泄漏,直接替换你ActiveMQ的bin/env文件中的JAVA_OPTS(备份后修改)。

操作步骤:
  1. 进入ActiveMQ安装目录的bin文件夹:cd 你的ActiveMQ路径/bin(例如:/opt/apache-activemq-6.0.1/bin);

  2. 备份原有env文件:cp env env.bak.jvm(区分之前的备份)

  3. 编辑env文件:vim env,删除原有JAVA_OPTS配置,粘贴以下完整配置:

JAVA_OPTS="-server \
-Djdk.virtualThreadScheduler.parallelism=1 \
-Djdk.virtualThreadScheduler.maxPoolSize=1 \
-Djava.util.concurrent.ForkJoinPool.common.parallelism=1 \
# 优化GC(保留原有配置,补充日志,适配8G内存)
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-XX:+PrintGCDetails \
-XX:+PrintGCTimeStamps \
-XX:+PrintHeapAtGC \
-XX:GCLogFileSize=100m \
-XX:NumberOfGCLogFiles=5 \
-Xloggc:${ACTIVEMQ_HOME}/logs/gc.log \
# 内存配置(适配8G服务器,保留原有Xms基础,提升稳定性)
-Xms1024m -Xmx2048m \
-Xmn1024m \
-XX:MetaspaceSize=512m \
-XX:MaxMetaspaceSize=1024m \
# 解决内存溢出、线程泄漏(补充原有配置缺失项)
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=${ACTIVEMQ_HOME}/logs/heapdump.hprof \
-Dsun.net.client.defaultConnectTimeout=10000 \
-Dsun.net.client.defaultReadTimeout=30000 \
# 其他优化(保留原有配置,补充ActiveMQ必要参数)
-Djava.net.preferIPv4Stack=true \
-Dactivemq.temp.directory=${ACTIVEMQ_HOME}/temp \
-Dactivemq.data=${ACTIVEMQ_HOME}/data \
$JAVA_OPTS"

关键说明:该配置完全保留你原有env中的核心参数(虚拟线程禁用、G1GC、基础内存配置),仅做3点适配优化:1. 适配8G服务器内存,将Xmx提升至2048m(2G)、Xmn提升至1024m,Metaspace参数同步扩容,避免内存瓶颈;2. 补充GC日志、内存溢出、线程泄漏相关参数,方便后续排查;3. 新增ActiveMQ临时目录、数据目录指定参数,提升稳定性。日志会输出到ActiveMQ的logs目录,不影响原有业务运行。

第二步:优化连接配置(解决连接泄露、CPU空转)

你的openwire端口(61616)配置了最大连接数10000,但未配置「连接超时回收」参数,客户端断开后连接未释放,导致连接堆积、CPU飙高,需优化transportConnector配置(仅修改openwire的uri,其他端口不变)。

操作步骤:
  1. 继续编辑activemq.xml,找到标签下的openwire配置;

  2. 将原有openwire的uri替换为以下内容(新增连接回收、超时参数):

<transportConnector name="openwire" uri="tcp://0.0.0.0:61616?maximumConnections=10000&amp;wireFormat.maxInactivityDuration=600000&amp;transport.keepalive=true&amp;keepAliveTime=600000&amp;transport.connectionTimeout=30000&amp;wireFormat.maxInactivityDurationInitalDelay=30000&amp;transport.closeAsync=true"/>

新增参数说明(核心作用):

  • transport.connectionTimeout=30000:连接超时时间30秒,避免无效连接占用资源;

  • wireFormat.maxInactivityDurationInitalDelay=30000:初始 inactive 检测延迟30秒,快速识别无效连接;

  • transport.closeAsync=true:异步关闭无效连接,避免阻塞线程,降低CPU占用。

注意:其他端口(amqp、stomp、mqtt+nio、ws)无需修改,保持原有配置即可。

第三步:添加监控+自动恢复脚本(兜底,避免手动重启)

配置完成后,添加一个定时监控脚本,实时检测ActiveMQ的CPU占用和连接状态,一旦CPU飙高(超过80%)或连接异常,自动重启ActiveMQ,无需手动干预,彻底解决「重启才能恢复」的问题。
推荐一起监控的核心指标(最实用)
下面这些都是能真实反映 ActiveMQ 已经挂了 / 假死的指标,而不是单纯看 CPU:
1. 端口监听是否存在(61616)
现象:进程还在,但端口没监听 → 客户端连不上
触发重启:61616 端口消失
2. Java 进程存在但不响应(假死)
现象:CPU 0% 或 100%,但不处理消息、不响应连接
触发重启:连续多次无法获取 CPU / 进程异常
3. 堆内存 OOM 前异常飙升
现象:内存一直涨、GC 疯狂跑
触发重启:堆内存使用率 > 90% 持续一段时间
4. ActiveMQ 日志出现致命错误
例如:java.lang.OutOfMemoryError、KahaDB lock、IOException
触发重启:关键异常关键字出现
5. 消息堆积严重(生产不消费)
队列消息数暴增、消费停滞
触发重启:队列深度 > 阈值持续一段时间
6. 文件句柄耗尽 / 连接数异常
连接数爆了、句柄满了 → 不再接受新连接
触发重启:打开文件数 > 80% 限制

操作步骤:
  1. 新建监控脚本:vim /你的ActiveMQ路径/bin/monitor_activemq.sh

  2. 粘贴以下脚本(替换脚本中的「你的ActiveMQ路径」为实际路径):

#!/bin/bash
# ActiveMQ 全能监控脚本
# 监控:CPU + 内存 + 端口 + FullGC + 文件句柄

AMQ_PATH="你的ActiveMQ路径"
CPU_THRESHOLD=85
MEM_THRESHOLD=90
FD_PERCENT=80
FULLGC_COUNT_THRESHOLD=10
CHECK_INTERVAL=60
LOG_PATH="${AMQ_PATH}/logs/monitor.log"

# 日志超过200M自动清空
log() {
  [ -f "$LOG_PATH" ] && [ $(du -b "$LOG_PATH" | awk '{print $1}') -ge 209715200 ] && > "$LOG_PATH"
  echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_PATH"
}

# 进程是否存在
is_running() {
  ps -ef | grep -E "activemq.*java" | grep -v grep >/dev/null 2>&1
}

# 获取PID
get_pid() {
  ps -ef | grep -E "activemq.*java" | grep -v grep | awk 'NR==1{print $2}'
}

# 获取CPU
get_cpu() {
  local pid=$(get_pid)
  [ -z "$pid" ] && echo 0 && return
  top -b -n 1 -p "$pid" | grep "$pid" | awk '{print $9}' | cut -d. -f1 || echo 0
}

# 获取内存
get_mem() {
  local pid=$(get_pid)
  [ -z "$pid" ] && echo 0 && return
  top -b -n 1 -p "$pid" | grep "$pid" | awk '{print $10}' | cut -d. -f1 || echo 0
}

# 端口是否监听
port_ok() {
  netstat -tuln | grep -w 61616 >/dev/null 2>&1
}

# 获取文件句柄使用率(%)
get_fd_usage() {
  local pid=$(get_pid)
  [ -z "$pid" ] && echo 0 && return
  local max=$(cat /proc/$pid/limits | grep -i "open files" | awk '{print $5}')
  local used=$(ls /proc/$pid/fd | wc -l)
  [ $max -le 0 ] && echo 0 && return
  echo $(( used * 100 / max ))
}

# 获取FullGC次数(jstat)
get_fullgc() {
  local pid=$(get_pid)
  [ -z "$pid" ] && echo 0 && return
  jstat -gc $pid | tail -n 1 | awk '{print $8}' || echo 0
}

# 重启ActiveMQ
restart_amq() {
  log "🚨 触发重启 ActiveMQ..."
  $AMQ_PATH/bin/activemq stop >/dev/null 2>&1
  sleep 10
  local pid=$(get_pid)
  [ -n "$pid" ] && kill -9 $pid >/dev/null 2>&1 && sleep 3
  $AMQ_PATH/bin/activemq start >/dev/null 2>&1
  sleep 10
  is_running && log "✅ 重启成功" || log "❌ 重启失败"
}

# 连续告警计数
ALERT_COUNT=0
LAST_FULLGC=0

log "=== 全能监控已启动 ==="

while true; do
  if ! is_running; then
    log "⚠️ 进程不存在,重启"
    restart_amq
    ALERT_COUNT=0
    sleep $CHECK_INTERVAL
    continue
  fi

  if ! port_ok; then
    log "⚠️ 61616 端口丢失,重启"
    restart_amq
    ALERT_COUNT=0
    sleep $CHECK_INTERVAL
    continue
  fi

  PID=$(get_pid)
  CPU=$(get_cpu)
  MEM=$(get_mem)
  FD=$(get_fd_usage)
  FULLGC=$(get_fullgc)

  # 计算1分钟内新增FullGC
  if [ $LAST_FULLGC -eq 0 ]; then
    LAST_FULLGC=$FULLGC
  fi
  FULLGC_INC=$(( FULLGC - LAST_FULLGC ))
  LAST_FULLGC=$FULLGC

  log "CPU:${CPU}% MEM:${MEM}% FD:${FD}% FullGC:+${FULLGC_INC}"

  # 异常判断
  HIGH=0
  [ $CPU -ge $CPU_THRESHOLD ] && HIGH=1
  [ $MEM -ge $MEM_THRESHOLD ] && HIGH=1
  [ $FD -ge $FD_PERCENT ] && HIGH=1
  [ $FULLGC_INC -ge $FULLGC_COUNT_THRESHOLD ] && HIGH=1

  if [ $HIGH -eq 1 ]; then
    ALERT_COUNT=$((ALERT_COUNT+1))
    log "⚠️ 第 ${ALERT_COUNT}/3 次异常"
  else
    [ $ALERT_COUNT -gt 0 ] && log "✅ 恢复正常,清空计数"
    ALERT_COUNT=0
  fi

  # 连续3次(3分钟)触发重启
  if [ $ALERT_COUNT -ge 3 ]; then
    log "🔥 持续异常,执行重启"
    restart_amq
    ALERT_COUNT=0
  fi

  sleep $CHECK_INTERVAL
done

第四步:logrotate日志轮转(长期管控,推荐生产环境使用)

利用Linux系统自带的logrotate工具,实现日志自动切割、压缩、保留历史日志、自动清理过期日志,无需手动干预,配置后永久生效,符合Linux服务器日志管理最佳实践。
操作步骤(直接复制执行即可):

  1. 创建logrotate配置文件(针对monitor.log单独配置):

vim /etc/logrotate.d/activemq_monitor;

  1. 粘贴以下配置(适配你的monitor.log路径,无需修改):

/opt/apache-activemq-6.0.1/logs/monitor.log {
daily # 每日轮转1次
rotate 7 # 保留7天的历史日志(可调整,如30保留30天)
compress # 对历史日志进行gzip压缩(节省磁盘空间)
missingok # 若日志文件不存在,不报错
copytruncate # 轮转时不停止脚本,避免监控中断(关键)
notifempty # 日志为空时,不进行轮转
dateext # 历史日志文件名添加日期后缀(如monitor.log-20260402)
maxsize 100M # 额外限制:日志超过100MB立即轮转(双重保障)
};

  1. 保存退出:按ESC,输入:wq,回车确认;

  2. 赋予配置文件正确权限(避免logrotate无法读取):

chmod 644 /etc/logrotate.d/activemq_monitor;

  1. 测试配置是否生效(模拟轮转,不实际执行):

logrotate -d /etc/logrotate.d/activemq_monitor;

若输出无报错,说明配置正常,logrotate会通过系统crontab定时执行(默认每天凌晨执行)。
1. 赋予脚本执行权限(替换你自己的ActiveMQ路径实际路径,直接复制执行):chmod +x /opt/apache-activemq-6.0.1/bin/monitor_activemq.sh

2. 设置脚本后台常驻(重启服务器后仍生效):
如果是修改先执行
# 停止旧脚本
ps -ef | grep monitor_activemq.sh | grep -v grep | awk ‘{print $2}’ | xargs kill -9
1. 编辑rc.local:vim /etc/rc.d/rc.local
2. 在文件末尾添加:nohup /opt/apache-activemq-6.0.1/bin/monitor_activemq.sh &

3. 赋予rc.local执行权限:chmod +x /etc/rc.d/rc.local

3. 启动监控脚本(正确命令,解决你遇到的提示):nohup /opt/apache-activemq-6.0.1/bin/monitor_activemq.sh &(后台运行,不会占用终端)。

脚本说明:
1)每60秒检查一次,CPU超过80%或61616端口异常
2)FullGC 频繁监控
每 60 秒获取一次 FullGC 次数
1 分钟内新增 ≥10 次 FullGC → 判定 GC 异常
持续 3 分钟 → 重启
2)文件句柄(FD)监控
获取进程最大允许打开文件数
获取当前已使用句柄数
使用率 ≥80% → 判定句柄即将耗尽
持续 3 分钟 → 重启
自动重启ActiveMQ,日志记录在ActiveMQ的logs/monitor.log,方便后续排查。

CPU + 内存 + 端口 + GC 频繁 + 文件句柄溢出
任意一项持续 3 分钟异常 → 自动重启。

补充:你执行脚本时的提示说明(正常现象,无需担心)

你执行命令 nohup /opt/apache-activemq-6.0.1/bin/monitor_activemq.sh 后,出现 nohup: ignoring input and appending output to ‘nohup.out’,这是 正常提示,原因如下:

  1. nohup 命令的作用是“忽略挂起信号,让脚本后台运行”,默认会将脚本的输出(日志)追加到当前目录的 nohup.out 文件中;

  2. 提示中“ignoring input”表示忽略终端输入,不影响脚本运行;“appending output to ‘nohup.out’”表示脚本输出(原本应打印到终端的内容)会写入 nohup.out 文件;

  3. 但我们的监控脚本已配置“日志写入 ActiveMQ 的 logs/monitor.log”,因此 nohup.out 文件中几乎不会有内容,无需关注该文件。

核心问题:你执行命令时 缺少末尾的 & 符号,导致脚本虽能后台运行,但会触发 nohup 的默认输出提示,且无法正常脱离终端。正确命令必须加上 &,即:nohup /opt/apache-activemq-6.0.1/bin/monitor_activemq.sh &,执行后终端可正常操作,脚本后台常驻。

三、最终执行顺序(必按此顺序,确保生效)

  1. 停止ActiveMQ:/opt/apache-activemq-6.0.1/bin/activemq stop

  2. 执行第一步:修改bin/env的JVM参数(路径:/opt/apache-activemq-6.0.1/bin/env);

  3. 执行第二步:修改activemq.xml,删除重复的managementContext(路径:/opt/apache-activemq-6.0.1/conf/activemq.xml);

  4. 执行第三步:优化openwire的uri配置(同第二步的activemq.xml文件);

  5. 执行第四步:部署监控脚本并启动(按上面的正确命令执行);

  6. 启动ActiveMQ:/opt/apache-activemq-6.0.1/bin/activemq start

  7. 查看启动状态:/opt/apache-activemq-6.0.1/bin/activemq status,确保启动成功。

四、关键注意事项(必看,避免踩坑)

  • 所有修改前务必备份配置文件(env、activemq.xml),避免误操作导致业务异常;

  • 结合你的8G服务器内存,JVM参数已优化为Xms1024m、Xmx2048m(适配内存且保留原有Xms基础),无需手动调整,该配置能充分利用服务器内存,提升ActiveMQ稳定性;

  • 监控脚本已固定你的ActiveMQ路径(/opt/apache-activemq-6.0.1),无需再手动替换,直接复制执行命令即可;

  • 启动监控脚本时,必须加上末尾的 & 符号,否则会触发nohup提示,且无法正常脱离终端;

  • 修改完成后,观察1-2小时,查看logs目录下的gc.log和monitor.log,确认无异常;

  • 无需修改你的业务配置(消息过期、死信队列、认证等),所有优化均不影响业务正常运行。

五、兜底排查(若仍有异常,执行此步骤)

若执行以上步骤后,CPU仍偶尔飙高,可查看日志排查具体原因:

  1. 查看GC日志:tail -f /opt/apache-activemq-6.0.1/logs/gc.log,排查是否有频繁GC、内存溢出;

  2. 查看监控日志:tail -f /opt/apache-activemq-6.0.1/logs/monitor.log,查看是否有自动重启记录;

  3. 查看ActiveMQ运行日志:tail -f /opt/apache-activemq-6.0.1/logs/activemq.log,排查是否有消息堆积、连接异常报错;

  4. 查看监控脚本运行状态:ps -ef | grep monitor_activemq.sh,若能看到脚本进程,说明脚本正常运行;若没有,重新执行启动命令。

按以上方案执行后,可彻底解决「CPU反复飙高、连接异常、需手动重启」的问题,无需再频繁重启ActiveMQ,且完全贴合你的现有配置,不影响业务运行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值