JVM 巡检脚本GC、线程与连接池该采哪些指标JVM 日常巡检不需要采集所有指标。GC 停顿、线程状态、堆外内存和连接池等待通常足以覆盖第一轮判断。脚本应设置超时并限制输出避免巡检本身影响业务进程。问题现象与排查入口老年代碎片化严重G1/CMS 垃圾回收器在长久运行后虽然总剩余内存很大但连续的大对象分配大数组/大 JSON频繁失败导致触发 Full GC 或 Concurrent Mode Failure。堆外内存Direct Memory泄露Netty 或 HTTP 客户端使用了Unsafe.allocateMemory/ DirectByteBuffer堆内存监控看似平稳但 Pod 物理内存逐渐达到 K8s Limit最终被系统直接 SIGKILL。线程处于锁死/争抢状态线程池满了之后被 Rejected但监控上 CPU 利用率低下排查时发现数十个线程锁在某个单例对象的 Synchronized 关键字上。走弯路的最大原因是依赖人工随机抽查。应把排障方法论固化为自动化的巡检脚本与指标洞察机制。自动化 JVM 巡检与诊断 Shell 脚本落地为了让巡检不走弯路可以编写一个在 Linux 生产节点或 Pod 内部运行的巡检脚本。该脚本基于 JDK 自带的jstat、jstack和jcmd命令行工具无需安装任何复杂的探针即可提取最关键的 JVM 健康度指标。#!/usr/bin/env bash # jvm_daily_inspection.sh - JVM 自动化巡检与隐患诊断脚本 set -euo pipefail PID$(pgrep -f java | head -n 1 || echo ) if [ -z ${PID} ]; then echo [ERROR] 未检测到正在运行的 Java 进程 exit 1 fi echo echo JVM 进程健康巡检报告 - PID: ${PID} echo 巡检时间: $(date %Y-%m-%d %H:%M:%S) echo # 1. 检查垃圾回收统计 (jstat) echo -- 1. 垃圾回收与内存占用率检查: JSTAT_OUT$(jstat -gcutil ${PID} 1000 1 | tail -n 1) S0$(echo ${JSTAT_OUT} | awk {print $1}) S1$(echo ${JSTAT_OUT} | awk {print $2}) E$(echo ${JSTAT_OUT} | awk {print $3}) O$(echo ${JSTAT_OUT} | awk {print $4}) M$(echo ${JSTAT_OUT} | awk {print $5}) YGC$(echo ${JSTAT_OUT} | awk {print $7}) YGCT$(echo ${JSTAT_OUT} | awk {print $8}) FGC$(echo ${JSTAT_OUT} | awk {print $9}) FGCT$(echo ${JSTAT_OUT} | awk {print $10}) echo - Eden 区使用率: ${E}% echo - 老年代使用率: ${O}% echo - 元空间使用率: ${M}% echo - Young GC 总次数: ${YGC} (累计耗时 ${YGCT}s) echo - Full GC 总次数: ${FGC} (累计耗时 ${FGCT}s) # 预警阈值判断 if (( $(echo ${O} 80.0 | bc -l) )); then echo [ALERT WARN] 老年代使用率高于 80% (${O}%)可能存在潜在泄漏 fi if [ ${FGC} -gt 5 ]; then echo [ALERT WARN] Full GC 次数已达到 ${FGC} 次请检查大对象分配 fi # 2. 检查线程状态与死锁 (jstack) echo -- 2. 线程池与死锁扫描: THREAD_DUMP$(jcmd ${PID} Thread.print) BLOCKED_COUNT$(echo ${THREAD_DUMP} | grep -c java.lang.Thread.State: BLOCKED || echo 0) WAITING_COUNT$(echo ${THREAD_DUMP} | grep -c java.lang.Thread.State: WAITING || echo 0) TOTAL_THREADS$(echo ${THREAD_DUMP} | grep -c tid || echo 0) echo - 总线程数: ${TOTAL_THREADS} echo - 阻塞 (BLOCKED) 线程数: ${BLOCKED_COUNT} echo - 等待 (WAITING) 线程数: ${WAITING_COUNT} if [ ${BLOCKED_COUNT} -gt 0 ]; then echo [CRITICAL WARN] 检出 ${BLOCKED_COUNT} 个处于 BLOCKED 状态的死锁/竞态线程 echo ${THREAD_DUMP} | grep -A 5 java.lang.Thread.State: BLOCKED fi # 3. 堆外内存概览 (jcmd NativeMemoryTracking) echo -- 3. NMT 堆外内存检查: if jcmd ${PID} VM.native_memory summary /tmp/nmt_summary.txt 21; then DIRECT_MEM$(grep -A 2 Other /tmp/nmt_summary.txt | grep committed | awk {print $2} || echo N/A) echo - 堆外分配追踪概况: ${DIRECT_MEM} else echo - NMT 未开启 (可通过 -XX:NativeMemoryTrackingsummary 开启) fi echo echo 巡检完成。将该脚本集成到 CI/CD 的定时任务或 DaemonSet 探针中每天自动巡检一次产出对比报告。避坑指南线上问题定位的四个防误区原则日常巡检与问题定位时有些看似合理的惯性思维实则是严重的避坑点排查环节常见错误做法 (误区)科学做法 (少走弯路)内存溢出 (OOM)遇到 OOM 立即重启服务以恢复可用性先保留現場通过-XX:HeapDumpOnOutOfMemoryError导出内存快照再自动重启CPU 持续升高只在应用日志里找 Exception用top -Hp pid找到高 CPU 的 LWP转为十六进制后在jstack中定位线程栈GC 耗时调优不看分配速率就直接增大堆先分析对象生命周期和分配热点再在同一负载下评估 ZGC、Shenandoah 或现有收集器线程数暴涨简单粗暴地上调 Tomcat / ThreadPool 的 Max Size寻找并发上游的响应延迟与阻塞点线程数增加只会加剧 CPU 上下文切换总结JVM 调优与排障的核心绝非背诵几十个冷门的-XX:启动参数而是建立起基于数据指标的防御体系。通过把排障经验编写为自动化的巡检脚本在老年代增长异动、线程阻塞初现时就主动介入才能避免在线上被动救火。