假死是进程未崩溃但不响应请求的状态,典型表现为r/s状态、kill-0成功但kill-15/9无效、cpu/内存长期为0;需通过ps查stat与wchan、kill-usr1测试、jstack/jstat分析线程与gc、ss查连接积压、slabtop看内核资源耗尽等分层定位阻塞点。

假死不是进程死了,而是它还在运行、没崩溃、没退出,但不再响应请求——就像人睁着眼却不动弹。这种状态往往比直接 OOM 更难定位,因为 dmesg 不报错、top 看起来一切正常、日志里也没有明显异常。核心思路是:先确认是否真“假死”,再分层锁定阻塞点,最后看是资源卡住、代码卡住,还是内核卡住。
怎么确认是假死而不是服务宕机
别一上来就重启或杀进程。假死的关键特征是:进程状态为 R(运行中)或 S(可中断睡眠),但无网络响应、无日志输出、HTTP 请求超时、数据库连接 hang 住。
- 用
curl -I --connect-timeout 2 http://localhost:8080/actuator/health测试端点是否响应;超时 ≠ 崩溃,更可能是线程阻塞或锁等待 - 查进程状态:
ps -o pid,ppid,stat,comm,wchan -p $PID,重点关注wchan列——它显示进程当前在哪个内核函数上等待,比如ep_poll(卡在 epoll 等待)、futex_wait_queue_me(卡在锁)、do_nanosleep(卡在 sleep) - 对比
kill -0 $PID和kill -USR1 $PID(对 Java 进程触发线程 dump):如果kill -0成功但USR1无响应,基本可断定 JVM 线程调度已停滞
Java Spring Boot 服务假死的高频卡点
Spring Boot 假死八成和线程池、连接池、GC 或 native 调用有关,不是内存爆了,而是某条链路彻底堵死。
在无 root/sudo 权限的环境(云容器、VPS、隔离主机)中安装并配置 OpenClaw 浏览器工具的 headless Chrome。适用场景:...
- 检查线程是否耗尽:
watch -n1 'jstack $PID | grep \"java.lang.Thread.State\" | wc -l',再配合jstack $PID | grep -A 10 \"BLOCKED\|WAITING\"找出被锁住的线程栈 - 确认数据库连接池是否空转:
curl http://localhost:8080/actuator/metrics/datasource.hikari.connections.active,如果active == max且idle == 0,同时waiting == 非零,说明所有连接都在用,新请求全在排队 - 看 GC 是否卡住:
jstat -gc $PID 1s,若FGC持续增长、FGCT时间飙升,或G1EvacuationPause耗时 >5s,说明 GC 线程本身被拖住,JVM 处于“半瘫痪”状态 - 警惕 DirectByteBuffer 泄漏:
jmap -histo $PID | grep DirectByteBuffer,数量持续上涨 +cat /proc/$PID/status | grep VmRSS明显高于堆大小,大概率是 Netty 或 NIO 导致的堆外内存卡死
Linux 内核层阻塞:别只盯着用户态
有时候应用层一切正常,但整个服务就是不响应——问题可能藏在内核调度、文件系统或网络栈里。
- 查是否有大量不可中断进程:
ps aux | awk '$8 ~ /D/ {print}',状态为D的进程无法被信号中断,常见于 NFS 挂载卡死、磁盘 I/O 故障、驱动 hang 住 - 看 socket 连接是否堆积:
ss -s查总连接数,ss -tnp state established | wc -l查 ESTABLISHED 数,再比对/proc/sys/net/core/somaxconn和应用层 accept 队列长度,若ss -ln sport = :8080显示Recv-Q持续 >0,说明连接积压在内核 backlog,accept 线程没在消费 - 检查 page cache 是否失控:
cat /proc/meminfo | grep -E "(Cached|SReclaimable|Shmem)",若Cached占用超 70% 且SReclaimable很低,说明缓存全是不可回收页(如 tmpfs、shared memory),物理内存实际已紧张,但free显示还很宽裕 - 留意
vm.swappiness设为 0 时的副作用:虽然禁用了 swap,但一旦匿名页暴涨(如 Java 堆外、mmap),页面回收机制会更激进地回收 page cache,反而导致磁盘读写毛刺加剧,间接拖慢响应
为什么 dmesg 里没 OOM 日志,但服务还是假死
OOM Killer 只在物理内存 + swap 彻底耗尽时才触发,而假死常发生在“内存还有剩、CPU 还有空、但关键资源已枯竭”的灰色地带——比如所有可用文件描述符被占满、所有 inotify 实例用完、或者内核 slab 中 ext4_inode_cache 耗尽导致新建文件失败,此时进程会卡在 sys_open 等待,dmesg 不记录,strace -p $PID 却能看到它停在 openat 系统调用上。
- 快速排查 fd 耗尽:
ls /proc/$PID/fd | wc -l对比cat /proc/$PID/limits | grep "Max open files" - 查 inotify 限制:
cat /proc/sys/fs/inotify/max_user_instances和cat /proc/$PID/status | grep -i inotify - 看 slab 是否异常:
slabtop -o | head -15,重点关注dentry、inode_cache、task_struct的use列是否远超常态(比如dentryuse > 2M) - 注意:这些资源瓶颈不会触发 OOM Killer,也不会写入
/var/log/kern.log,但足以让 Spring Boot 的健康检查、配置刷新、甚至日志落盘全部 hang 住
最易被忽略的一点:假死经常是多个小瓶颈叠加的结果——比如线程池耗尽 + 连接池耗尽 + 文件描述符接近上限,每个单独看都不致命,合起来就让服务彻底失语。排查时不要满足于找到“一个原因”,要横向扫一遍资源水位,尤其关注那些不报错、不告警、但会悄悄堵死请求链路的内核资源项。










