本文目录导读:

Java 告警案例通常涉及线上故障排查、性能瓶颈分析和系统稳定性治理,下面我会从典型场景、具体案例、排查思路和告警规则优化四个方面,结合实战经验为你梳理。
典型案例分类
高频 Full GC / GC 长时间停顿
现象:告警信息“GC 时间超过 500ms”、“Old Gen 使用率 > 90%”。
案例:某电商大促期间,订单服务出现频繁 Full GC,单次 STW(Stop-The-World)长达 2 秒,导致接口超时率飙升。
根因分析:
- 内存泄漏:缓存了未设置过期时间的对象(如将订单详情放入静态 Map 且未清理)。
- 大对象直接进入老年代:批量查询返回超大 List(超过
-XX:PretenureSizeThreshold)。 - MetaSpace 膨胀:动态生成大量代理类(如 CGLIB 父类泄漏)。
解决对策:
# 使用 JDK 自带工具查看堆内对象 jmap -histo:live [pid] | head -20 # 或使用 MAT / VisualVM 分析 dump 文件 jmap -dump:format=b,file=heap.hprof [pid]
在代码中排查并修正异常的大对象或未释放的引用,调整 JVM 参数(如加大新生代、设置 G1 的 MaxGCPauseMillis)。
线程池耗尽(RejectedExecutionException)
现象:告警“线程池队列已满,任务拒绝”。
案例:文件处理服务使用 Executors.newFixedThreadPool(10),但每个任务耗时较长(如依赖外部 FTP 上传),高峰期导致队列积压 10 万+,最终触发拒绝策略。
根因分析:
- IO 密集型任务误用 CPU 密集配置。
- 未做超时控制:HTTP 调用/数据库连接等待时间过长,线程被长期占用。
解决对策:
- 使用有界队列(
ArrayBlockingQueue)并自定义拒绝策略(如降级、重试或持久化到 MQ)。 - 对于 IO 密集型任务,适当调大线程数(如
2 * CPU 核心数)。 - 给外部调用增加 ReadTimeout / ConnectTimeout,并配合 线程池监控(活跃线程数、队列积压量)。
数据库连接池连接泄漏
现象:告警“HikariCP 连接获取超时”、“连接数达到最大值”。
案例:分页查询服务在循环中执行 SQL 时,因为 try-with-resources 使用不当,导致连接未释放,最终连接池被打满,整个服务假死。
根因分析:
- 代码中手动获取
Connection后未在finally中关闭。 - 异常提前
return,跳过释放代码。 - 慢 SQL 占住连接不放。
解决对策:
- 强制使用
try-with-resources或@Transactional托管连接。 - 开启连接池的泄漏检测:
spring: datasource: hikari: leak-detection-threshold: 60000 # 超过60秒未关闭则告警 - 开启慢 SQL 日志,优化 SQL 索引。
内存中的“幽灵对象”导致的 OOM(OutOfMemoryError)
现象:告警“java.lang.OutOfMemoryError: Java heap space”或“GC overhead limit exceeded”。
案例:报表服务从 Kafka 消费数据时,将原始报文存储在本地 ThreadLocal 中,但消费线程被复用,导致数据残留膨胀。
根因分析:
- ThreadLocal 未 remove(在线程池场景是重灾区)。
- 静态集合持有大对象。
解决对策:
- 在线程池任务中,
finally中务必调用ThreadLocal.remove()。 - 建议使用
-XX:+HeapDumpOnOutOfMemoryError和-XX:HeapDumpPath保存现场,便于事后 MAT 分析。
告警排查通用方法论
-
看 JVM 状态:
jstat -gcutil [pid] 1000
观察 YGC、FGC、堆内存占用趋势。
-
看线程状态:
jstack [pid] > thread_dump.txt
重点查找
BLOCKED、WAITING状态的线程,以及是否大量线程卡在HTTP或DB调用上。 -
看接口 RT 分布:通过 APM(如 SkyWalking、Pinpoint)追踪有问题的链路,定位是自身代码还是下游依赖耗时。
-
看中间件指标:Redis 超时、MQ 消费堆积、数据库活跃连接数。
告警规则优化建议
避免“狼来了”:告警需要可运行、可处置。
| 告警维度 | 建议阈值(示例) | 说明 |
|---|---|---|
| FGC 频率 | 连续 3 次,且间隔 < 5 分钟 | 比单次触发更有价值 |
| FGC 时长 | > 1 秒 | 结合业务容忍度调整 |
| 线程池活跃度 | 活跃线程 > 80% 且持续 10 分钟 | 排除瞬时尖峰 |
| 错误日志 | Error 级别日志 5 分钟内超过 50 条 | 按接口维度聚合 |
| Open API 接口 RT | P99 > 2000ms 持续 3 分钟 | 关注长尾影响而非均值 |
核心原则:告警需要基于多指标(如 CPU + GC + 响应时间)叠加,减少误报。
一个完整的实战案例演示
场景:某支付服务在 12:00 告警,报“C2C 转账接口 P99 上升至 5s”。
- 紧急止血:先增大下游队列容量,或者切流量到备用机房(若有多活)。
- 定位:
- 通过
jstat查看 GC,发现 FGC 频率升高。 - 通过
jmap dump分析,发现 大量的String对象占据了 70% 堆空间——原来是日志框架误将HttpServletRequest的 Body 全部toString()打印。
- 通过
- 解决:修改日志策略,只打印必要字段(或压缩日志),增加 JVM 堆内存。
- 复盘:告警规则增加“FGC 时长 > 1s 且伴随接口 RT 上升”的组合告警,避免以后再次发生。
处理 Java 告警的关键在于快速定位(JVM + 线程 + 链路),根源修复(代码 + 参数),以及持续优化(压测 + 监控)。
如果你遇到某个具体的报错(OutOfMemoryError: Metaspace 或 RejectedExecutionException),可以告诉我具体日志,我可以帮你进一步分析。