Java日常巡检案例

wen java案例 1

Java日常巡检实战案例:从Full GC风暴到线程阻塞的7个排查路径

Java日常巡检案例

目录导读

  1. 巡检前的准备:监控指标与工具链清单
  2. Full GC频率突增——JVM堆内存泄漏的定位
  3. 接口RT飙升——线程池阻塞与死锁诊断
  4. 数据库连接池耗尽——连接泄露的Log分析
  5. 巡检自动化:脚本+告警的闭环设计
  6. Q&A高频问答:解决你巡检中的真实困惑

巡检前的准备:别把“巡检”做成“救火”

在日常运维中,最怕的是“平时不烧香,急时抱佛脚”,一次合格的Java巡检,应当在业务低峰期(如凌晨2点)执行,并采集以下黄金指标:

  • JVM层:堆内存使用率(老年代占比)、GC暂停时间、存活对象大小。
  • 线程层:活跃线程数、BLOCKED/WAITING状态线程分布、锁等待队列长度。
  • 中间件层:Tomcat/Netty的线程池活跃度、数据库连接池活跃链接数。

工具建议jstat -gcutil 查看GC曲线,jstack 抓取线程快照,Arthas 在线诊断,同时开启JMX远程监控,并用Prometheus+Grafana实现可视化。

案例一:Full GC频率从1小时/次飙升至5分钟/次

现象:告警系统提示Full GC次数异常,且老年代内存回收后仍持续上涨。

排查步骤

  1. 执行 jmap -histo:live <pid> 查看存活对象Top10,发现 byte[]LocalDateTime 对象数量异常。
  2. 通过 jmap -dump:format=b,file=heap.hprof 导出堆快照,用MAT分析支配树,定位到某业务线程持有的 静态HashMap 未清理,且Key为请求IP(导致永不失效)。
  3. 修复措施:改用 Caffeine 本地缓存并设定过期策略,同时增加 -XX:+UseG1GC 避免内存碎片。

启示:巡检时应当重点关注老年代占用率曲线,若连续3次降不下去,基本判定泄漏。

案例二:订单接口P99从200ms暴涨至5s

现象:压测时发现线程池队列积压,但CPU使用率仅15%。

诊断路径

  1. 执行 jstack <pid> 三次,间隔10秒,发现同一业务锁 OrderLock 上有大量 BLOCKED 线程。
  2. 进一步查看代码,发现同步块内调用了 外部HTTP服务,且未设置超时时间(默认无限等待)。
  3. 解决方案:改用 ReentrantLock.tryLock(2s),并将HTTP调用改为异步化,同时启用 Hystrix 熔断。

巡检建议:每次巡检需抓取 jstack 两次以上,对比线程状态是否持续卡死;同时检查 JVM 参数 -Djava.net.preferIPv4Stack=true 是否设置,避免DNS解析延迟。

案例三:数据库连接池从50个连接耗尽

现象:应用日志报 Cannot get a connection, pool exhausted,但DB侧活跃连接数并不高。

根因:某定时任务中 Connection 未在 finally 块中关闭,且 HikariCPmaxLifetime 小于MySQL的 wait_timeout 导致连接被DB回收后应用仍持有。

巡检技巧

  • 使用 SHOW PROCESSLIST 对比应用侧连接数。
  • 在代码中启用 HikariCPleakDetectionThreshold(如10秒)自动检测泄露SQL。
  • 通过 BtraceArthastrace 命令动态追踪 getConnection 的调用栈。

巡检自动化:将人力从“看图表”中解放

成熟团队会编写Shell/Python脚本,实现:

  • 每日凌晨自动采集 jstat, jstack, gc.log
  • awk 统计GC累计暂停时间,若超过300ms则触发邮件告警。
  • 利用 ElasticSearch 存储历史快照,实现趋势对比(如“本周FullGC次数较上周上升30%”)。

关键检查项:磁盘空间(/tmp 是否被heap dump占满)、文件句柄数(lsof | wc -l 是否接近 ulimit 限制)。

Q&A高频问答

Q1:巡检中发现Metaspace一直在涨,但回收不掉,怎么办? A:先用 jstat -gcmetacapacity 查看扩容情况,大部分原因是CGLIB代理类或反射生成类过多,建议检查代码是否大量使用Proxy.newProxyInstance(),并设置 -XX:MaxMetaspaceSize 限制,同时配合 -XX:+TraceClassLoading 定位触发源。

Q2:如何区分“正常波动”和“故障前兆”? A:推荐“3σ原则”——建立7天基线(如CPU均值30%,标准差5%),当监控值超过均值+3倍标准差时触发告警,对GC而言,正常CMS的remark阶段暂停应在50ms内,若超过则视为异常。

Q3:巡检时发现 Java 17 相比 Java 8 的线程调度差异,需要调整参数吗? A:重点检查两点:-XX:+UseZGC-XX:+UseShenandoahGC 是否适合低延迟场景;Java 17的默认GC是G1,但需要显式设置 -XX:MaxGCPauseMillis=200,另外Java 17的Thread.onSpinWait() 在自旋锁场景下显著降低CPU占用。


Java日常巡检不是“点开JConsole看一眼”,而是对JVM、线程、网络、代码逻辑的系统性审计,通过上述案例,希望你能建立一套可复用的巡检SOP——先看指标、再抓线程、后析堆栈、最后改代码,每一次Full GC的背后,都藏着一个未被释放的引用;每一次RT的飙升,都有一行未设超时的代码,巡检的价值在于提前发现这些“隐形定时炸弹”,而非事后firefighting。

抱歉,评论功能暂时关闭!