List zombie processes
Find defunct child processes whose parent forgot to `wait()` for them — the classic "Z" state — for triaging fork-bomb-like leaks, badly-written supervisors, and PID-table exhaustion incidents.
How to list zombie processes in each shell
ps -eo pid,ppid,stat,comm | awk '$3 ~ /^Z/'`ps -eo COLS` selects custom columns; `stat` (or `s`) prints the STATE letters (R/S/D/Z/T/X/I plus flags like `+` foreground, `<` high priority). `awk '$3 ~ /^Z/'` matches state starting with Z (`Z`, `Z+`, `Zl`). To also see the reaping parent: include `ppid` and then `ps -o comm= -p $PPID` to identify which supervisor failed.
ps -eo pid,ppid,stat,comm | awk '$3 ~ /^Z/'ps -eo pid,ppid,stat,comm | awk '$3 ~ /^Z/'Get-CimInstance Win32_Process | Where-Object { $_.ExecutionState -eq 7 } | Select ProcessId, ParentProcessId, NameWindows does NOT have Unix zombies. The closest concept: a process that has EXITED but a handle is still open by another process — kernel keeps the EPROCESS struct alive until last handle closes. `Win32_Process.ExecutionState=7` is "Terminated" but rarely populated by Windows. Practical answer: **Windows has no zombie problem** — the kernel reaps automatically. If pwsh is on Linux/macOS, fall back to `ps`.
tasklist /v /fo csv | findstr /i "Not Responding"cmd has no concept of zombies. "Not Responding" in `tasklist /v` is the closest analog — a Windows process whose message pump has hung; UI threads stuck. It is NOT a zombie (process is still ALIVE, just unresponsive). Use `taskkill /F /PID N` to force-terminate. For genuine zombie work on Windows, you don't need to do anything; there isn't a problem to solve.
Equivalents listed for Bash, Zsh, Fish, PowerShell, cmd.exe.
Gotchas & notes
- **What a zombie actually is**: a Unix child process that has EXITED (`_exit(2)` called) but whose parent has NOT yet called `wait(2)` / `waitpid(2)` to collect the exit status. The kernel keeps the PID + exit code in the process table so the parent can read it later. Memory is freed, no CPU usage, no file descriptors — JUST a PID-table entry. **Zombies are not a CPU/memory problem; they are a PID-EXHAUSTION problem.** `/proc/sys/kernel/pid_max` (default 32768 or 4194304) caps total live PIDs (alive + zombie). Hit the cap → `fork()` returns ENOMEM → new processes fail across the WHOLE SYSTEM.
- **Reap zombies by signalling the parent, not the zombie**: `kill -9 $ZOMBIE_PID` does NOTHING — you can't kill an already-dead process. The parent must call `wait()`. To prompt the parent, send `SIGCHLD`: `kill -CHLD $PPID`. Most parents have a `SIGCHLD` handler that calls `wait()`; broken parents do not. If `kill -CHLD` doesn't clear the zombie, the parent is buggy — last-resort fix is to **kill the parent**: zombies are inherited by `init`/PID 1, which always reaps. `kill -9 $PPID` → zombie's `ppid` becomes 1 → init reaps within ~1 tick. Be sure killing the parent is acceptable (it may take down a service).
- **ps STATE column letters — full reference**: `R` running/runnable, `S` interruptible sleep (waiting for event), `D` uninterruptible sleep (usually disk I/O), `Z` zombie, `T` stopped (SIGSTOP), `t` stopped by debugger, `X` dead (transient, you rarely see it), `I` idle kernel thread. **Modifiers**: `<` high priority (negative nice), `N` low priority (nice > 0), `L` pages locked in memory, `s` session leader, `l` multi-threaded, `+` foreground process group. `Z+` = zombie in a foreground group. `Dl` = uninterruptible sleep with multiple threads. These letters are POSIX-defined; macOS BSD ps uses the same alphabet.
- **Container PID 1 must reap zombies — common Docker pitfall**: in a container, your application binary often IS PID 1. PID 1 has a special role: it adopts orphans AND is responsible for reaping them. If your app doesn't handle `SIGCHLD`, every short-lived child (shell-invoked subprocess, `system()` call) becomes a zombie that NEVER reaps. Over hours, PID table fills, container becomes unable to fork. **Fix**: use `--init` flag (`docker run --init …`) which inserts `tini` as PID 1 (small reaper that wraps your app); or in Kubernetes `spec.shareProcessNamespace: true` + a sidecar init; or in Dockerfile `ENTRYPOINT ["tini", "--"]`. Languages with built-in supervisors (Node.js via `child_process`, Go via `os/exec.Wait()`) reap correctly IF you call the wait; manually-spawned children must be `.Wait()`-ed.
- **High zombie count signals which app is broken**: `ps -eo ppid,stat | awk '$2~/^Z/ {print $1}' | sort | uniq -c | sort -rn` → counts zombies grouped by parent PID, highest first. Look up the parent: `ps -p $PPID -o comm,cmdline`. Common offenders: badly-written supervisors that `fork()` workers but never wait, shell scripts that `&` jobs without `wait`, custom init systems in Docker images that don't handle SIGCHLD. The fix is at the parent (add the `wait()` call); the symptom (zombies in `ps`) goes away as the kernel reaps them.
Related commands
Related tasks
- Kill a process by name— Terminate every running process whose executable matches a given name.
- Find a process ID by name— Look up the PID(s) of a running process given its executable name — the lookup that precedes most kill / signal / inspect operations.
- Send a signal to a process— Deliver a specific Unix signal to a process — useful for graceful shutdown, config reload, and triggered behaviour.
- Count the running processes— Report how many processes are alive — useful for load testing, capacity baselining, or detecting fork-bomb / runaway-parallelism conditions.
- Show the process tree— Render the parent/child relationships between running processes — useful for tracing rogue children, debugging fork bombs, and understanding shell-spawned subprocess chains.