[{"data":1,"prerenderedAt":333},["ShallowReactive",2],{"\u002Fjava-interview\u002Ftroubleshooting":3,"java-interview-search:\u002Fjava-interview\u002Ftroubleshooting":288},{"id":4,"title":5,"body":6,"category":274,"cover":275,"date":276,"description":277,"extension":278,"group":279,"meta":280,"navigation":281,"order":282,"path":283,"seo":284,"sitemap":285,"stem":286,"__hash__":287},"javaInterview\u002Fjava-interview\u002Ftroubleshooting.md","线上排障：CPU、内存与依赖故障",{"type":7,"value":8,"toc":260},"minimark",[9,13,18,37,52,55,58,62,65,76,83,90,94,97,100,103,106,110,121,124,127,134,138,141,144,147,150,154,157,160,167,170,174,177,180,183,186,190,193,196,199,202,205,218,221,224],[10,11,12],"p",{},"排障题考的是「有没有一套稳定的动作」，而不是背工具名。通用顺序是：先确认影响面和最近变更，再用指标缩小范围，然后取证，最后止血与复盘。下面所有命令都应在测试环境先验证，生产执行前评估影响。",[14,15,17],"h2",{"id":16},"_1-线上-cpu-飙高怎么排查","1. 线上 CPU 飙高怎么排查？",[10,19,20,21,25,26,29,30,29,33,36],{},"先看整体：",[22,23,24],"code",{},"top"," 确认是哪个进程，观察 ",[22,27,28],{},"us","、",[22,31,32],{},"sy",[22,34,35],{},"wa"," 哪个高。用户态高通常是业务计算或 GC；系统态高可能是系统调用、锁竞争或上下文切换。",[10,38,39,40,43,44,47,48,51],{},"再定位线程：",[22,41,42],{},"top -Hp \u003Cpid>"," 找到最耗 CPU 的线程，把十进制 id 转成十六进制，在 ",[22,45,46],{},"jstack \u003Cpid>"," 里按 ",[22,49,50],{},"nid=0x..."," 搜索。连续抓两三次，看它是否停在同一个方法。",[10,53,54],{},"业务代码的常见原因：死循环、正则灾难性回溯、超大集合排序、频繁 JSON 序列化、日志拼接、加密计算。如果是 GC 线程占用高，转去看 GC 日志和堆使用。",[10,56,57],{},"止血手段包括限流、回滚最近发布、摘除异常实例；不要直接重启了事，先保留线程栈和堆信息，否则根因就丢了。",[14,59,61],{"id":60},"_2-内存持续上涨怎么定位","2. 内存持续上涨怎么定位？",[10,63,64],{},"先区分堆内和堆外。看 GC 后老年代是否回落：如果每次 Full GC 后仍然上涨，怀疑泄漏；如果 GC 后能回落但很快又涨，可能是流量或缓存策略问题。",[10,66,67,68,71,72,75],{},"堆内取证用 ",[22,69,70],{},"jcmd \u003Cpid> GC.heap_dump"," 导出，配合 ",[22,73,74],{},"-XX:+HeapDumpOnOutOfMemoryError"," 自动保存；用 MAT 看支配树、对象数量和 GC Root 路径，重点找「本该释放却被集合或线程持有」的对象。",[10,77,78,79,82],{},"堆外要看直接内存（NIO、Netty）、元空间（动态生成类）、线程栈和 JIT 代码缓存。",[22,80,81],{},"jcmd VM.native_memory summary"," 需要启动时开启 NMT，Netty 可以开泄漏检测。",[10,84,85,86,89],{},"常见根因：本地缓存无上限、",[22,87,88],{},"ThreadLocal"," 未清理、监听器只增不减、批量查询一次加载过多数据。修复前先确认对象的生命周期，不要用「调大堆」掩盖泄漏。",[14,91,93],{"id":92},"_3-接口突然变慢排查顺序是什么","3. 接口突然变慢，排查顺序是什么？",[10,95,96],{},"先看影响范围：是所有接口还是某个接口，是单实例还是全部实例，是否与发布、配置变更、流量高峰同时发生。这一步能排除大半方向。",[10,98,99],{},"再看链路：用 traceId 找到耗时最长的那一跳。如果时间花在数据库，看慢查询、锁等待和连接池；花在远程调用，看下游 RT、超时和重试；花在应用内，看线程池队列、锁竞争和 GC 停顿。",[10,101,102],{},"注意平均值会骗人，要看 P95、P99 和错误率。重试和超时配置不当会让下游变慢放大成雪崩，表现为「所有服务一起慢」。",[10,104,105],{},"还要检查资源：CPU 是否打满、连接数是否耗尽、磁盘是否打满、网络是否有丢包。定位后用限流、扩容、降级或回滚止血，再做根因修复。",[14,107,109],{"id":108},"_4-频繁-full-gc-怎么处理","4. 频繁 Full GC 怎么处理？",[10,111,112,113,116,117,120],{},"先确认频率和原因：用 GC 日志看每次回收前后的占用、停顿时间、晋升量，以及是否伴随 ",[22,114,115],{},"GC overhead limit exceeded"," 或 ",[22,118,119],{},"promotion failed","。",[10,122,123],{},"如果是内存泄漏，堆 dump 会显示老年代对象持续增长，必须修代码；如果是堆太小或大对象过多，评估扩容和对象生命周期；如果是元空间不足，检查动态代理、脚本引擎和热部署。",[10,125,126],{},"G1 下还要关注 Humongous 对象：超过 Region 一半的大对象会占用连续 Region，频繁创建大数组会触发并发周期甚至 Full GC。",[10,128,129,130,133],{},"处理顺序是：先止血（扩容、限流、回滚），再取证（GC 日志、堆 dump），最后修复。不要只调 ",[22,131,132],{},"-Xmx","，那只是把问题推迟。",[14,135,137],{"id":136},"_5-线程池打满有什么表现怎么止损","5. 线程池打满有什么表现？怎么止损？",[10,139,140],{},"表现通常是接口 RT 上升、超时增多、队列长度持续增长、拒绝数上升，严重时上游开始熔断。异步任务则表现为「提交成功但很久不执行」。",[10,142,143],{},"先看监控：活跃线程数、队列长度、完成任务数、拒绝次数、任务耗时分布。再看线程栈，确认线程卡在数据库、远程调用还是锁上。",[10,145,146],{},"止血可以临时扩容、提高消费能力、把非核心任务摘出去，或者对入口限流。根因通常是下游变慢、任务无界提交、线程池之间互相阻塞，或者同步调用被放进了异步池。",[10,148,149],{},"长期要做池隔离（核心与非核心分开）、有界队列、明确拒绝策略、按下游容量设计并发，并给队列等待时间设上限。",[14,151,153],{"id":152},"_6-数据库连接池耗尽怎么办","6. 数据库连接池耗尽怎么办？",[10,155,156],{},"先看连接池指标：活跃连接、空闲连接、等待线程数、获取连接超时次数。活跃连接长期占满且不释放，通常是长事务或连接泄漏。",[10,158,159],{},"长事务的常见来源：事务里做远程调用、批量处理过多数据、手动开启事务后异常路径没提交\u002F回滚。连接泄漏则是借出后没有在 finally 中归还。",[10,161,162,163,166],{},"用数据库侧的证据交叉验证：",[22,164,165],{},"SHOW PROCESSLIST"," 看连接状态和执行的 SQL，慢查询日志找长时间运行的语句，锁等待也会让连接一直占用。",[10,168,169],{},"止血可以回滚、杀掉异常会话、临时扩容，但根本解决要缩短事务、修复泄漏、优化慢 SQL。连接池不是越大越好，超过数据库承载能力只会让争用更严重。",[14,171,173],{"id":172},"_7-redis-或-mq-故障时怎么降级","7. Redis 或 MQ 故障时怎么降级？",[10,175,176],{},"先明确依赖等级：哪些功能可以短时间不可用，哪些必须保底。缓存故障时，可以用本地缓存兜底、限流保护数据库、对非核心读请求返回默认值；同时防止「缓存失效后全部打到数据库」的雪崩。",[10,178,179],{},"消息队列故障时，生产端可以降级为同步调用、写入本地消息表稍后补发，或暂时拒绝非核心消息；消费端要保证重复消费幂等，恢复后按积压量平滑追平，避免瞬间打满下游。",[10,181,182],{},"降级开关要提前做好并演练，包含触发条件、影响范围、恢复步骤和责任人。不要等故障时才临时改代码。",[10,184,185],{},"所有降级都要有监控和告警：降级本身是「有损可用」，必须让团队知道当前处于降级状态。",[14,187,189],{"id":188},"_8-发布出问题怎么回滚如何减少这类事故","8. 发布出问题怎么回滚？如何减少这类事故？",[10,191,192],{},"先判断能否快速回滚：应用版本回滚通常最快；数据库变更往往不可逆，所以要做「扩展-收缩」式变更，先加字段、双写、再切读、最后删旧字段，保证新旧版本兼容。",[10,194,195],{},"发布策略上，灰度或金丝雀发布先放少量流量，观察错误率、延迟和关键业务指标，再逐步扩大。功能开关可以把「发布」和「上线」分开，出问题时关开关比回滚更快。",[10,197,198],{},"回滚前要保留现场：日志、指标、trace、异常堆栈。回滚后确认数据一致性，尤其是消息、缓存和异步任务是否留下中间状态。",[10,200,201],{},"事后复盘关注可行动的改进：补充监控、加自动化检查、缩小变更批次、完善回滚脚本。把「这次是谁的错」换成「哪个环节让错误没被更早发现」。",[14,203,204],{"id":204},"易错点与自测",[206,207,208,212,215],"ul",{},[209,210,211],"li",{},"排障先止血再根因，但止血前尽量保留线程栈、堆 dump、GC 日志等现场。",[209,213,214],{},"堆内存上涨不等于堆泄漏，直接内存、元空间和线程栈要单独看。",[209,216,217],{},"扩连接池、扩线程池常常只是把压力转移到更下游。",[10,219,220],{},"自测：① 给出一次 CPU 高的完整命令链；② 说明如何区分内存泄漏和容量不足；③ 设计一个缓存故障时的三级降级方案。",[14,222,223],{"id":223},"参考资料",[206,225,226,236,244,252],{},[209,227,228,235],{},[229,230,234],"a",{"href":231,"rel":232},"https:\u002F\u002Fdocs.oracle.com\u002Fen\u002Fjava\u002Fjavase\u002F21\u002Fdocs\u002Fspecs\u002Fman\u002Fjcmd.html",[233],"nofollow","jcmd 手册（Java 21）"," — 线程栈、堆信息、堆转储与 JFR",[209,237,238,243],{},[229,239,242],{"href":240,"rel":241},"https:\u002F\u002Fdocs.oracle.com\u002Fen\u002Fjava\u002Fjavase\u002F21\u002Ftroubleshoot\u002F",[233],"Java SE 21 故障排查指南"," — OOM、GC 与原生内存问题",[209,245,246,251],{},[229,247,250],{"href":248,"rel":249},"https:\u002F\u002Fsre.google\u002Fsre-book\u002Fmanaging-incidents\u002F",[233],"Google SRE Book：应急响应"," — 事件分级、止血与复盘",[209,253,254,259],{},[229,255,258],{"href":256,"rel":257},"https:\u002F\u002Fgithub.com\u002Fbrettwooldridge\u002FHikariCP",[233],"HikariCP 配置说明"," — 连接池容量与超时参数",{"title":261,"searchDepth":262,"depth":262,"links":263},"",2,[264,265,266,267,268,269,270,271,272,273],{"id":16,"depth":262,"text":17},{"id":60,"depth":262,"text":61},{"id":92,"depth":262,"text":93},{"id":108,"depth":262,"text":109},{"id":136,"depth":262,"text":137},{"id":152,"depth":262,"text":153},{"id":172,"depth":262,"text":173},{"id":188,"depth":262,"text":189},{"id":204,"depth":262,"text":204},{"id":223,"depth":262,"text":223},"java-interview",null,"2026-10-24","用一套可复用的排查顺序处理 CPU 飙高、内存上涨、接口变慢、Full GC、线程池与连接池耗尽及发布回滚","md","分布式与工程",{},true,"160","\u002Fjava-interview\u002Ftroubleshooting",{"title":5,"description":277},{"loc":283},"java-interview\u002Ftroubleshooting","snEyVpoV3RgdiGAcIubOnCYjxNAiDLd86_rM8oGo59k",[289,293,297,301,305,309,313,317,321,325,329],{"id":283,"title":5,"titles":290,"content":291,"level":292},[],"用一套可复用的排查顺序处理 CPU 飙高、内存上涨、接口变慢、Full GC、线程池与连接池耗尽及发布回滚 排障题考的是「有没有一套稳定的动作」，而不是背工具名。通用顺序是：先确认影响面和最近变更，再用指标缩小范围，然后取证，最后止血与复盘。下面所有命令都应在测试环境先验证，生产执行前评估影响。",1,{"id":294,"title":17,"titles":295,"content":296,"level":262},"\u002Fjava-interview\u002Ftroubleshooting#_1-线上-cpu-飙高怎么排查",[5],"先看整体：top 确认是哪个进程，观察 us、sy、wa 哪个高。用户态高通常是业务计算或 GC；系统态高可能是系统调用、锁竞争或上下文切换。 再定位线程：top -Hp \u003Cpid> 找到最耗 CPU 的线程，把十进制 id 转成十六进制，在 jstack \u003Cpid> 里按 nid=0x... 搜索。连续抓两三次，看它是否停在同一个方法。 业务代码的常见原因：死循环、正则灾难性回溯、超大集合排序、频繁 JSON 序列化、日志拼接、加密计算。如果是 GC 线程占用高，转去看 GC 日志和堆使用。 止血手段包括限流、回滚最近发布、摘除异常实例；不要直接重启了事，先保留线程栈和堆信息，否则根因就丢了。",{"id":298,"title":61,"titles":299,"content":300,"level":262},"\u002Fjava-interview\u002Ftroubleshooting#_2-内存持续上涨怎么定位",[5],"先区分堆内和堆外。看 GC 后老年代是否回落：如果每次 Full GC 后仍然上涨，怀疑泄漏；如果 GC 后能回落但很快又涨，可能是流量或缓存策略问题。 堆内取证用 jcmd \u003Cpid> GC.heap_dump 导出，配合 -XX:+HeapDumpOnOutOfMemoryError 自动保存；用 MAT 看支配树、对象数量和 GC Root 路径，重点找「本该释放却被集合或线程持有」的对象。 堆外要看直接内存（NIO、Netty）、元空间（动态生成类）、线程栈和 JIT 代码缓存。jcmd VM.native_memory summary 需要启动时开启 NMT，Netty 可以开泄漏检测。 常见根因：本地缓存无上限、ThreadLocal 未清理、监听器只增不减、批量查询一次加载过多数据。修复前先确认对象的生命周期，不要用「调大堆」掩盖泄漏。",{"id":302,"title":93,"titles":303,"content":304,"level":262},"\u002Fjava-interview\u002Ftroubleshooting#_3-接口突然变慢排查顺序是什么",[5],"先看影响范围：是所有接口还是某个接口，是单实例还是全部实例，是否与发布、配置变更、流量高峰同时发生。这一步能排除大半方向。 再看链路：用 traceId 找到耗时最长的那一跳。如果时间花在数据库，看慢查询、锁等待和连接池；花在远程调用，看下游 RT、超时和重试；花在应用内，看线程池队列、锁竞争和 GC 停顿。 注意平均值会骗人，要看 P95、P99 和错误率。重试和超时配置不当会让下游变慢放大成雪崩，表现为「所有服务一起慢」。 还要检查资源：CPU 是否打满、连接数是否耗尽、磁盘是否打满、网络是否有丢包。定位后用限流、扩容、降级或回滚止血，再做根因修复。",{"id":306,"title":109,"titles":307,"content":308,"level":262},"\u002Fjava-interview\u002Ftroubleshooting#_4-频繁-full-gc-怎么处理",[5],"先确认频率和原因：用 GC 日志看每次回收前后的占用、停顿时间、晋升量，以及是否伴随 GC overhead limit exceeded 或 promotion failed。 如果是内存泄漏，堆 dump 会显示老年代对象持续增长，必须修代码；如果是堆太小或大对象过多，评估扩容和对象生命周期；如果是元空间不足，检查动态代理、脚本引擎和热部署。 G1 下还要关注 Humongous 对象：超过 Region 一半的大对象会占用连续 Region，频繁创建大数组会触发并发周期甚至 Full GC。 处理顺序是：先止血（扩容、限流、回滚），再取证（GC 日志、堆 dump），最后修复。不要只调 -Xmx，那只是把问题推迟。",{"id":310,"title":137,"titles":311,"content":312,"level":262},"\u002Fjava-interview\u002Ftroubleshooting#_5-线程池打满有什么表现怎么止损",[5],"表现通常是接口 RT 上升、超时增多、队列长度持续增长、拒绝数上升，严重时上游开始熔断。异步任务则表现为「提交成功但很久不执行」。 先看监控：活跃线程数、队列长度、完成任务数、拒绝次数、任务耗时分布。再看线程栈，确认线程卡在数据库、远程调用还是锁上。 止血可以临时扩容、提高消费能力、把非核心任务摘出去，或者对入口限流。根因通常是下游变慢、任务无界提交、线程池之间互相阻塞，或者同步调用被放进了异步池。 长期要做池隔离（核心与非核心分开）、有界队列、明确拒绝策略、按下游容量设计并发，并给队列等待时间设上限。",{"id":314,"title":153,"titles":315,"content":316,"level":262},"\u002Fjava-interview\u002Ftroubleshooting#_6-数据库连接池耗尽怎么办",[5],"先看连接池指标：活跃连接、空闲连接、等待线程数、获取连接超时次数。活跃连接长期占满且不释放，通常是长事务或连接泄漏。 长事务的常见来源：事务里做远程调用、批量处理过多数据、手动开启事务后异常路径没提交\u002F回滚。连接泄漏则是借出后没有在 finally 中归还。 用数据库侧的证据交叉验证：SHOW PROCESSLIST 看连接状态和执行的 SQL，慢查询日志找长时间运行的语句，锁等待也会让连接一直占用。 止血可以回滚、杀掉异常会话、临时扩容，但根本解决要缩短事务、修复泄漏、优化慢 SQL。连接池不是越大越好，超过数据库承载能力只会让争用更严重。",{"id":318,"title":173,"titles":319,"content":320,"level":262},"\u002Fjava-interview\u002Ftroubleshooting#_7-redis-或-mq-故障时怎么降级",[5],"先明确依赖等级：哪些功能可以短时间不可用，哪些必须保底。缓存故障时，可以用本地缓存兜底、限流保护数据库、对非核心读请求返回默认值；同时防止「缓存失效后全部打到数据库」的雪崩。 消息队列故障时，生产端可以降级为同步调用、写入本地消息表稍后补发，或暂时拒绝非核心消息；消费端要保证重复消费幂等，恢复后按积压量平滑追平，避免瞬间打满下游。 降级开关要提前做好并演练，包含触发条件、影响范围、恢复步骤和责任人。不要等故障时才临时改代码。 所有降级都要有监控和告警：降级本身是「有损可用」，必须让团队知道当前处于降级状态。",{"id":322,"title":189,"titles":323,"content":324,"level":262},"\u002Fjava-interview\u002Ftroubleshooting#_8-发布出问题怎么回滚如何减少这类事故",[5],"先判断能否快速回滚：应用版本回滚通常最快；数据库变更往往不可逆，所以要做「扩展-收缩」式变更，先加字段、双写、再切读、最后删旧字段，保证新旧版本兼容。 发布策略上，灰度或金丝雀发布先放少量流量，观察错误率、延迟和关键业务指标，再逐步扩大。功能开关可以把「发布」和「上线」分开，出问题时关开关比回滚更快。 回滚前要保留现场：日志、指标、trace、异常堆栈。回滚后确认数据一致性，尤其是消息、缓存和异步任务是否留下中间状态。 事后复盘关注可行动的改进：补充监控、加自动化检查、缩小变更批次、完善回滚脚本。把「这次是谁的错」换成「哪个环节让错误没被更早发现」。",{"id":326,"title":204,"titles":327,"content":328,"level":262},"\u002Fjava-interview\u002Ftroubleshooting#易错点与自测",[5],"排障先止血再根因，但止血前尽量保留线程栈、堆 dump、GC 日志等现场。 堆内存上涨不等于堆泄漏，直接内存、元空间和线程栈要单独看。 扩连接池、扩线程池常常只是把压力转移到更下游。 自测：① 给出一次 CPU 高的完整命令链；② 说明如何区分内存泄漏和容量不足；③ 设计一个缓存故障时的三级降级方案。",{"id":330,"title":223,"titles":331,"content":332,"level":262},"\u002Fjava-interview\u002Ftroubleshooting#参考资料",[5],"jcmd 手册（Java 21） — 线程栈、堆信息、堆转储与 JFR Java SE 21 故障排查指南 — OOM、GC 与原生内存问题 Google SRE Book：应急响应 — 事件分级、止血与复盘 HikariCP 配置说明 — 连接池容量与超时参数",1793263560088]