Java服务器无响应,根因集中在线程池耗尽、内存溢出、死锁、GC频繁或外部依赖阻塞五个方向,80%的线上故障都能从线程快照和GC日志里找到线索。
Java服务假死?先看线程快照怎么抓
服务器无响应不等于进程崩溃,多数情况是“假死”进程还活着,但请求全部卡住,第一步不是重启,而是抓现场。
抓现场的三板斧
- jstack输出线程快照:
jstack <pid> > threaddump.txt,间隔5秒连续抓3次,对比线程状态变化。 - jstat看GC情况:
jstat -gcutil <pid> 1000 10,观察Full GC频率和耗时。 - top -Hp看线程CPU:定位哪个线程在烧CPU,再用
printf "%x" 线程PID转十六进制,去线程快照里找对应线程栈。
用信号强制输出线程栈
如果jstack命令都敲不出结果,用kill -3 <pid>强制JVM把线程快照打到标准输出,这个信号连完全卡死的JVM都能响应。
线程快照怎么看
| 线程状态 | 含义 | 处理方向 |
|---|---|---|
| WAITING / BLOCKED | 线程卡在锁或等待条件 | 检查死锁和锁竞争 |
| RUNNABLE | 线程在持续执行 | 检查CPU密集逻辑 |
| TIMED_WAITING | 线程在sleep或wait超时 | 多数正常,看数量是否异常堆积 |
行业共识认为,线程快照是定位Java服务无响应问题的第一手证据,没有线程栈分析,后续所有排查都是盲人摸象。
线程池用尽是最常见的无响应元凶
大量请求涌入时,线程池如果设计不合理,所有线程都去处理慢请求,新请求只能排队,队列堆满后触发拒绝策略,服务对外表现就是卡死。
线程池满的典型症状
- 日志中出现
RejectedExecutionException或ThreadPoolExecutor相关报错。 - 线程快照里大量线程处于
WAITING状态,且都卡在ThreadPoolExecutor.getTask()。 - Tomcat默认线程池200个,如果200个线程全在等待下游响应,服务就”堵死”了。
定位线程池问题实操步骤
- 用jstack抓线程快照,搜索
http-nio或http-bio- 统计处于
WAITING状态的线程数量,如果接近线程池最大值,说明线程池已被打满。- 看这些线程卡在哪个调用链上是数据库连接池获取连接、是HTTP调用下游接口,还是Redis读取。
- 统计处于
解决方案分三层

- 调大线程池:临时应急有效,但治标不治本,线程多了反而增加上下文切换开销。
- 快速失败和降级:给慢调用设置超时时间,超时直接返回兜底数据,不让请求无休止堆积。
- 压测确定合理池大小:公式是
线程数 = CPU核数 × (1 + 等待时间/计算时间),但真实业务环境最好通过压测决定参数。
内存溢出导致GC疯狂运转
服务无响应还有一种常见情况:内存快满了,JVM在疯狂做Full GC,但每次回收效果甚微,此时GC线程占用大量CPU,业务线程几乎得不到执行机会。
怎么确认是GC导致的假死
用jstat -gcutil观察:
- Full GC次数急剧增加,比如从原来几分钟一次变成几秒一次。
- GC耗时占比(FGCT/SCT)超过15%,说明JVM大部分时间都在做垃圾回收。
- 老年代(O区)使用率持续在95%以上,回收后下降不明显。
常见内存泄漏场景
- Map缓存只增不减:用HashMap做缓存,但没有清除策略。
- ThreadLocal未清理:线程池中的ThreadLocal在任务结束后没有
remove(),导致对象一直被线程引用。 - 大对象频繁创建:每笔请求创建几十MB的临时对象,直接进老年代。
Heap Dump分析路径
确认是GC问题后,抓Heap Dump:jmap -dump:format=b,file=heap.hprof <pid>,然后用MAT或VisualVM分析:
- 看支配树(Dominator Tree)定位最大的对象。
- 查可疑的集合对象,比如某一个Map持有几万个实例且不断增长。
- 沿着对象引用链找到GC Roots,看是谁引用了这些对象不让回收。
业内专家指出,Java应用假死原因里,内存泄漏占比相当高,尤其在长时间运行的线上服务中,定期做Heap Dump分析对预防无响应很有价值。
死锁和锁竞争把线程全部锁死
多个线程互相持有对方需要的锁,谁也等不到资源,服务就僵在那里。
死锁的识别方法
线程快照中出现明显的循环等待:
"http-nio-1" prio=5 tid=0x00007f - waiting to lock <0x0000001234abcd>
locked <0x0000001234efgh>
"http-nio-2" prio=5 tid=0x00007f - waiting to lock <0x0000001234efgh>
locked <0x0000001234abcd>
jstack会在死锁检测里直接输出Found one Java-level deadlock提示。
锁竞争导致的服务假死
死锁是极端情况,更多是锁竞争太激烈

,大量线程等待同一把锁,典型场景:
- 用
synchronized保护大段代码,锁粒度太粗。 - 业务逻辑在持锁状态下调用远程接口或数据库操作。
- 多个线程抢同一个静态锁或单例对象的锁。
解决锁问题的思路
- 缩小锁范围:只锁需要保护的临界区,不要在锁内做IO操作。
- 用读写锁或并发容器:读多写少的场景用
ReentrantReadWriteLock或ConcurrentHashMap替代全局锁。 - 代码评审中重点检查锁的嵌套调用,避免在持锁时调用其他加锁方法形成隐式死锁。
CPU飙高导致请求处理不过来
服务器无响应还有一种情况,CPU已经被某个线程占满,业务线程分配不到时间片。
CPU 100%的常见原因
- 死循环或空转:代码里某个
while(true)没有退出条件,或者for循环遍历集合时误用了size()方法导致O(n²)。 - 正则表达式灾难性回溯:复杂正则遇到特殊输入时回溯次数爆炸,CPU被消耗殆尽。
- 序列化/反序列化:使用低效的序列化框架处理超大对象列表。
- 频繁GC:GC线程本身会消耗CPU,内存压力大时GC占用的CPU甚至超过业务线程。
排查CPU飙高的实操流程
top -Hp <pid>找出CPU占比最高的线程ID。printf "%xn" 线程ID转换为十六进制。- 在jstack输出中搜索这个十六进制线程ID,找到对应的堆栈。
- 观察堆栈中是哪个类哪个方法在持续执行。
代码层面的修复
- 死循环:增加循环上限或退出条件,使用定时任务监控线程执行时长。
- 正则回溯:把复杂正则简化,或者先做长度校验再匹配。
- 序列化:换用Protobuf等二进制序列化方案,减少CPU消耗。
外部依赖阻塞导致请求全部排队
Java服务器无响应不一定是自身代码的问题,可能是下游依赖把请求堵住了。
依赖阻塞的典型链路
- 数据库连接池耗尽:连接池默认最大20个连接,某个慢SQL把连接全部占住,其他请求获取连接时无限等待。
- Redis阻塞:Redis执行
KEYS命令或大key操作导致阻塞,所有读取线程卡住。 - HTTP调用超时设置过长:下游接口响应慢,上游服务因为没有设置合理的超时时间,线程全部挂在等待响应上。
- 消息队列积压
:消费速度跟不上生产速度,消费者的线程池全忙。
依赖阻塞的排查手段
用jstack看线程栈,如果大量线程卡在java.net.SocketInputStream.read()或java.sql.Connection相关等待,就去检查对应的依赖服务。
防护手段
- 给所有外部调用设置连接超时和读取超时,建议连接超时3秒、读取超时5秒起步。
- 数据库连接池设置获取连接的等待超时,默认
connectionTimeout配5000ms即可。 - 使用信号量或熔断器做线程隔离,下游故障时快速失败而不是无限等待。
Java服务器无响应怎么排查:推荐的一套标准流程
前面拆了各个独立的成因,实际排查时建议按下面这个顺序操作。
第一步:收集现场证据
先抓线程快照、GC日志、堆内存使用情况、CPU占用记录,这四样缺一不可,一般优先抓线程快照,因为它能直接反映出问题是线程卡顿还是GC密集。
第二步:分析线程状态分布
打开线程快照,统计各类状态占比:
- WAITING和BLOCKED占比过高 → 锁或资源等待问题,进入依赖链路排查。
- RUNNABLE占比高且集中在同一代码块 → 死循环或CPU密集计算。
- TIMED_WAITING异常多 → 大部分线程在等待网络或IO响应。
第三步:结合GC日志交叉验证
线程快照看到大量线程在GC相关的类中执行,立刻看GC日志确认Full GC频率,堆内存使用率持续高位,结合Header Heap获取堆内大对象分布。
第四步:按优先级逐一排除
先处理CPU飙高问题,再看GC和内存泄漏,然后排查线程池和外部依赖,死锁放最后观察相关。
相关问答
Java服务器无响应时应该先重启还是先排查?
先抓现场再重启,如果服务完全没有恢复迹象,可以先抓线程快照和GC日志,然后重启恢复业务,如果你直接重启,现场证据就没了,下次再出问题还是两眼一抹黑。
Java应用假死和宕机有什么区别?
假死是进程还活着,端口还在监听,但请求无法被处理,表现为超时或无响应;宕机是进程直接退出,端口不存在,连接直接被拒,假死的定位难度更高,因为需要分析线程状态和内存数据才能找出原因。
为什么Java服务频繁无响应但服务器CPU和内存看起来都正常?
此时重点查看线程状态分布,如果把全部线程卡在等待锁或等待下游响应,CPU刚好是空闲的,建议用jstack输出线程快照,统计WAITING状态线程数,并检查数据库连接池和外部HTTP调用的耗时分布。
图片来源于AI模型,如侵权请联系管理员。作者:酷小编,如若转载,请注明出处:https://www.kufanyun.com/ask/907200.html

