当前位置:首页>Linux>凌晨 3 点,Linux 杀掉了我们的核心服务

凌晨 3 点,Linux 杀掉了我们的核心服务

  • 2026-10-12 07:11:17
凌晨 3 点,Linux 杀掉了我们的核心服务

书接上文内存排查第一步:停止迷信 free。

在上一篇文章中,我们提到了检测内存问题的几种手段,如果截止此时,都没有解决内存增长问题,那么可能就会出现本文的主题--OOM。

当 OOM killer被触发时,它的行为绝非随机——这是内核最后的“保命机制”。

我们可以通过如下命令查看完整内核日志细节:

journalctl -k | grep -A 30 "Out of memory"

这条命令会输出三大关键信息,相当于内核决策前的“现场取证快照”:

  • • 故障发生时的系统内存状态
  • • 每个进程的内存使用情况
  • • 内核选择杀死某个进程的底层逻辑

比如,我的本机输出如下:

Out of memory: Kill process 30449 (xxx) score 435 or sacrifice childkernel: Killed process 30449 (xxx), UID 0, total-vm:4907316kB, anon-rss:3589616kB, file-rss:788kB, shmem-rss:0kBkernel: brand_bmss_serv invoked oom-killer: gfp_mask=0x201da, order=0, oom_score_adj=0kernel: brand_bmss_serv cpuset=/ mems_allowed=0kernel: CPU: 3 PID: 14357 Comm: brand_bmss_serv Kdump: loaded Not tainted 3.10.0-1160.6.1.el7.x86_64 #1kernel: Hardware name: OpenStack Foundation OpenStack Nova, BIOS rel-1.10.2-0-g5f4c7b1-20240428_141054-szxrtosci10000 04/01/2014kernel: Call Trace:kernel:  [<ffffffffae181400>] dump_stack+0x19/0x1bkernel:  [<ffffffffae17bd20>] dump_header+0x90/0x229kernel:  [<ffffffffc00651f8>] ? vp_set+0xb8/0xf0 [virtio_pci]kernel:  [<ffffffffc02f34b0>] ? update_balloon_size.isra.8+0x40/0x60 [virtio_balloon]kernel:  [<ffffffffadbc20cd>] oom_kill_process+0x2cd/0x490kernel:  [<ffffffffadbc1abd>] ? oom_unkillable_task+0xcd/0x120kernel:  [<ffffffffadbc27ba>] out_of_memory+0x31a/0x500kernel:  [<ffffffffae17c83d>] __alloc_pages_slowpath+0x5db/0x729kernel:  [<ffffffffadbc8db6>] __alloc_pages_nodemask+0x436/0x450kernel:  [<ffffffffadc18a18>] alloc_pages_current+0x98/0x110kernel:  [<ffffffffadbbdb87>] __page_cache_alloc+0x97/0xb0kernel:  [<ffffffffadbc0b20>] filemap_fault+0x270/0x420kernel:  [<ffffffffc020b756>] ext4_filemap_fault+0x36/0x50 [ext4]kernel:  [<ffffffffadbede7a>] __do_fault.isra.61+0x8a/0x100kernel:  [<ffffffffadbee42c>] do_read_fault.isra.63+0x4c/0x1b0kernel:  [<ffffffffadbf5c70>] handle_mm_fault+0xa20/0xfb0kernel:  [<ffffffffae18f653>] __do_page_fault+0x213/0x500kernel:  [<ffffffffae18fa26>] trace_do_page_fault+0x56/0x150kernel:  [<ffffffffae18efa2>] do_async_page_fault+0x22/0xf0kernel:  [<ffffffffae18b7a8>] async_page_fault+0x28/0x30kernel: Mem-Info:kernel: active_anon:1788752 inactive_anon:45648 isolated_anon:0         active_file:4635 inactive_file:4633 isolated_file:0         unevictable:0 dirty:932 writeback:135 unstable:0kernel: Out of memory: Kill process 30450 (xxx) score 436 or sacrifice childkernel: Killed process 30450 (xxx), UID 0, total-vm:4907316kB, anon-rss:3592852kB, file-rss:1096kB, shmem-rss:0kB

可能有人会问我如何知道哪个进程可能会产生OOM?

我们可以使用如下命令:

for pid in /proc/[0-9]*; do  score=$(cat $pid/oom_score 2>/dev/null)  name=$(cat $pid/comm 2>/dev/null)  echo "$score $name $pid"done | sort -rn | head -15

内核会给每个进程分配一个分数oom_score。分数越高,OOM的概率越大。

输出如下:

254 xxx /proc/28758202 yyy /proc/2038713 xxx /proc/493212 java /proc/72612 aaa /proc/48308 gunicorn /proc/180028 gunicorn /proc/180018 gunicorn /proc/180008 gunicorn /proc/17999

也就是说上面进程ID 28758这个进程产生OOM的风险最大。

对于某些核心进程,如果我们不想其被OOM Killer掉,我们可以通过 oom_score_adj 来影响内核的决策,保护核心进程,或指定优先被杀死的非核心进程。

# 保护核心进程,使其永远不会被 OOM killer 杀死(-1000 = 永不杀死)echo -1000 > /proc/<PID>/oom_score_adj# 让非核心进程成为 OOM 优先杀死目标(+1000 = 优先杀死)echo 1000 > /proc/<PID>/oom_score_adj

数值范围:

  • • -1000——永不杀戮
  • • 0- 默认
  • • +1000先杀

如果保护过多进程,内核可能会被迫杀死更关键的进程(比如系统核心服务),反而加剧故障。

除了上面这种方式,我们还可以通过调整参数**vm.overcommit**以防止内存溢出 (OOM)

Linux 允许进程分配的内存超过系统实际可用的物理内存,这种行为称为“内存过度提交(memory overcommit)”。

这种机制有时能正常工作,但有时会延迟故障爆发,直到系统彻底崩溃。我们可以通过 vm.overcommit_memory 控制这种行为。

cat /proc/sys/vm/overcommit_memory

有如下三种值:

  • • 0:启发式(默认)—— 内核会估算内存分配是否安全,优先允许分配
  • • 1:始终过度提交 —— 内核允许所有内存分配请求(风险高,易触发 OOM)
  • • 2:严格过度提交 —— 内核拒绝超过指定限制的内存分配请求(最可控)

我们可以将值改为2(严格模式),并设置合理的过度提交比例:

sysctl vm.overcommit_memory=2sysctl vm.overcommit_ratio=80

当模式为 2 时,Linux 会强制执行一个硬性内存分配限制,计算公式如下:

CommitLimit(分配上限)=(物理内存 × 过度提交比例 / 100)+ 交换分区(Swap)

举个例子,便于理解:

若系统配置为:32GB 物理内存 + 8GB 交换分区,过度提交比例设为 80,则:

可分配内存上限 ≈(32 × 0.8)+ 8 = 33.6GB

超过这个上限,新的内存分配请求会直接失败,而非延迟到最后触发 OOM 杀死进程,更便于提前排查问题。

通过以下命令查看当前内存提交情况,判断是否接近分配上限:

grep Commit /proc/meminfo

比如输出:

CommitLimit:    33423360 kBCommitted_AS:   22020096 kB

其中:

  • • CommitLimit:系统允许分配的最大内存(即上述公式计算的上限)
  • • Committed_AS:所有进程已申请的总内存(内核承诺分配的内存总量)

在严格模式(vm.overcommit_memory=2)下,如果 Committed_AS 超过 CommitLimit,后续的内存分配请求会直接失败。

不过,有时候,内存压力并非由内存泄漏或工作负载峰值导致,而是由**透明大页(Transparent Huge Pages,THP)**引起的。THP 是内核的一项特性,它使用更大的内存页(2MB 而非默认的 4KB),通过减少 TLB 未命中来提升性能。

大多数 Linux 发行版默认启用 THP,但它会给数据库(Redis、PostgreSQL、MongoDB 均建议禁用)带来难以解释的延迟峰值和内存膨胀,还可能导致难以追踪的异常内存使用模式。

cat /sys/kernel/mm/transparent_hugepage/enabled# Output:[always] madvise never

其中:

  • • always,所有进程均已启用 THP
  • • madvise,仅在应用程序主动请求时启用
  • • never,完全禁用THP

对性能要求较高的系统应避免使用always。

使用下面这个命令来查看大页的使用情况:

grep -E "AnonHugePages|HugePages" /proc/meminfo

输出如下:

AnonHugePages:    446464 kBHugePages_Total:       0HugePages_Free:        0HugePages_Rsvd:        0HugePages_Surp:        0

其中:

  • • AnonHugePages:当前由 THP 支持的内存大小,数值较大说明 THP 正被积极使用
  • • HugePages_Total:静态预留的 HugeTLB 大页数(手动配置)
  • • HugePages_Free:未使用的预留大页数
  • • HugePages_Rsvd:已预留但尚未使用的大页数
  • • HugePages_Surp:超出配置数量的多余大页数

接着,我们聊最后一个关键点:交互分区。

Swap(交换分区)是内存不足时系统的“安全网”。但和所有安全网一样,如果频繁依赖它,说明系统存在问题。我们的目标不是“零 Swap 使用”,而是可控、可预测的 Swap 行为。

健康的 Swap 应该是“安静且无波澜”的。如果它变得活跃且频繁读写,说明上游存在需要修复的问题:

  • • Swap 使用量稳定 —— 正常状态
  • • Swap 持续增长 —— 存在内存压力
  • • 系统频繁交换 —— 内存配置不足或存在内存泄漏

使用下面这个命令,来查看哪些进程使用了Swap:

for pid in /proc/[0-9]*/status; do  name=$(awk '/^Name:/{print $2}' $pid)  swap=$(awk '/^VmSwap:/{print $2}' $pid)  [ "${swap:-0}" -gt 1024 ] && echo "${swap} kB  $name"done 2>/dev/null | sort -rn | head -10

输出如下:

2097152 kB  java 524288 kB  python3  65536 kB  postgres

真正的线上稳定性,从来不是等 OOM 发生后再救火,而是提前看懂内核的信号、管好内存的边界、避开那些看不见的坑。

看懂日志、预判风险、守住底线、保护核心,这才是一个成熟工程师面对内存危机时最该有的底气。

愿我们的服务永远稳定运行,再无突如其来的 OOM。

以上

如果对本文有疑问可以加笔者微信直接交流,笔者也建了C/C++相关的技术群,有兴趣的可以联系笔者加群。

推荐阅读点击标题可跳转

1、内存排查第一步:停止迷信 free

2、一个编程小技巧

3、为什么编译器会生成多个析构函数

最新文章

随机文章