Build1 distinct publisher3 min readUpdated
Kubernetes calls it OOMKilled. Linux calls it overcommit coming due, and a badness score nobody in the deploy pipeline has looked at decides who pays.
The Engineer · Build desk
Compiled by The EngineerSomething wrong?How this is made
The useful question after a 137 is not why the application asked for so much memory. It is which of the two killers fired, because they have different triggers and neither fix lives in the application repo [11][12].
Start with the bet. When a process calls `malloc()`, the kernel usually hands back a virtual address range without checking that physical memory exists to back it, a behaviour called overcommit and controlled by `vm.overcommit_memory`, which defaults to heuristic mode 0 [3][4]. The reason is `fork()`: a process holding 4GB of RSS that forks would, under strict accounting, have to reserve a second 4GB it will almost certainly never touch because of copy-on-write [5]. That is 8GB booked to run one 4GB workload [1], and refusing the fork would break an enormous amount of software that relies on cheap forking [5]. So the kernel defers the reckoning until pages are actually touched rather than requested [6].
The deferral is why there is nothing to catch. When the kernel genuinely cannot satisfy a page fault, something has to die immediately and synchronously inside the page allocation path [6], which is why the process gets a SIGKILL with no exception, no core dump and no shutdown hook [1], and why the only trace on a host-level kill is one line in `dmesg` [2].
Victim selection is the part that decides how long the incident runs. The kernel does not kill the process whose allocation pushed the system over the edge; it kills whichever process scores worst on a badness heuristic, readable at `/proc/<pid>/oom_score` [7]. That score is roughly resident memory plus swap as a share of total system memory, then adjusted by `oom_score_adj`, a value from -1000 to 1000 that defaults to 0 [8]. A process pinned at -1000 is treated as unkillable and survives even while using 90% of RAM, which is how `sshd` and `systemd` stay up so you can log in afterwards [9]. Read that in reverse: anything in your critical path that has never been killed has been surviving because it happened to be smaller than a noisier neighbour, unless somebody set the adjustment on purpose [8][9].
Inside a container the denominator changes. Kubernetes writes a memory limit as `memory.max` in the pod's cgroup v2 hierarchy, and when the cgroup's usage crosses that ceiling the kernel runs an OOM kill scoped to processes in that cgroup alone [10]. Host memory is irrelevant to that decision, which is why a pod dies while `free -h` on the node looks comfortable [11]. On bare metal or a plain VPS the victim pool is every process on the box instead [14].
Hence the one diagnostic that matters. Host kills surface through `dmesg -T | grep -i 'killed process'`; cgroup kills surface through `journalctl -k` and the `oom_kill` counter in `memory.events` under the cgroup path [12]. A climbing `oom_kill` counter alongside a flat node memory graph is a limit set too tight, not a leak [13]. Check the counter first and the fix is a number in a manifest; skip it and you are reading heap profiles for an allocation that was never the problem, which is the gap the dev.to write-up puts at five minutes versus three hours [15].
Follow any of these and your For You feed starts watching them — no settings page required.
Ranked by verification strength, evidence, and original report placement.
A rising oom_kill counter with a stable node-level memory graph is the signature of a cgroup limit that is too tight, not a real leak.
A kernel OOM kill appears in Kubernetes as exit code 137 with status OOMKilled: a SIGKILL from the kernel with no stack trace, no exception, no core dump, no warning and no graceful shutdown hook.
A host-level kill leaves a single dmesg line of the form 'Out of memory: Killed process 4821 (node)'.
Linux's default allocation strategy is optimistic: when a process calls malloc(), the kernel usually returns a virtual address range immediately without checking whether enough physical memory exists to back it. This is overcommit, controlled by vm.overcommit_memory.
vm.overcommit_memory values: 0 is heuristic overcommit and the default, 1 always overcommits and never refuses an allocation, 2 is strict accounting that refuses once the commit limit is hit.
Overcommit exists because of fork(): a process using 4GB of RSS that forks would, under strict accounting, need to reserve another 4GB it will almost certainly never touch thanks to copy-on-write, and refusing that fork would break enormous amounts of software that relies on cheap forking.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Single-source but checkable mechanics
All claims come from one dev.to explainer with no citations, kernel version references or external corroboration. What raises the score above the single-source floor is that the substantive claims are specific and independently verifiable on any Linux host — vm.overcommit_memory values, /proc/<pid>/oom_score and oom_score_adj ranges, cgroup v2 memory.max, /sys/fs/cgroup/<path>/memory.events oom_kill — and are internally consistent. What caps it is the absence of any measurement, incident record or second publisher, plus asserted heuristics and severity framing with no data.
No adoption signal in sources
The cluster contains no release, deployment, benchmark, pricing, licensing, security-incident or usage-disclosure evidence. The article describes long-standing kernel and Kubernetes behaviour without reporting how widely any practice, setting or tool is used, so no adoption level can be measured without inventing facts.
Mechanics grounded, severity framing stretched
Slightly overstated overall. The technical body makes no promotional or novelty claims — it explains documented kernel behaviour and gives commands to verify it — so the bulk of the content is aligned with its evidence. The small positive gap comes from the framing layer: the five-minute-fix versus three-hour-incident contrast and the confident 'signature of a cgroup limit that's too tight' diagnosis are presented as settled while resting on assertion, and the piece states behaviour without kernel-version or cgroup-version qualifiers that would bound its generality.
Low commercial pressure, mild authorship incentive
The single source is an individual post on a developer community platform. It promotes no vendor, product, paid tier, model or service, discloses no sponsorship, and makes no pricing or funding claims; the recommendations point at native kernel and Kubernetes settings rather than anything purchasable. The residual incentive is the ordinary community-platform one to write confident, quotable explainer content, which plausibly explains the unquantified severity framing.
Moderate: verifiable content, one publisher
Confidence is moderate. The mechanics are the kind of claim a reader can confirm on a test host, and the article is internally coherent, which supports the core claims. But the cluster has exactly one publisher, no adoption dimension can be measured at all, and the interpretive claims are unsupported by data, so the assessment cannot be held with high confidence.
build
Partition, not consolidation: what a 43-minute Jenkins queue actually cost1 distinct publisher
build
Rate limit your MCP servers, because a retrying agent turns one error into a billing incident1 distinct publisher
build
912MB to 108MB is mostly typing now, and that weakens the base-image excuse in review1 distinct publisher
build
Your meter now runs on someone else's machine: signed receipts, fsync, and failing open1 distinct publisher
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 23, 2026