You need to enable JavaScript to run this app. C语言Linux环境下内存耗尽时OOM前的提示处理方法
优惠活动
大模型
产品
解决方案
定价
更多

C语言Linux环境下内存耗尽时OOM前的提示处理方法

问题核心根因

你遇到的进程被直接终止、p==NULL判断失效的问题,本质是Linux默认的内存过度提交(Overcommit)机制导致的:

  • Linux默认采用启发式Overcommit策略,调用malloc申请大内存时,内核不会立刻校验实际物理内存+交换空间是否充足,只会给进程返回一段合法的虚拟内存地址,哪怕这个地址实际根本没有对应的物理内存支撑。
  • 只有当你后续通过memset真正写入这段内存、触发内核缺页异常时,内核才会尝试分配真实物理页。如果这时候发现已经没有可用内存了,内核会直接触发OOM Killer发送不可捕获的SIGKILL信号终止进程,整个过程根本不会让之前的malloc返回NULL,你写的内存不足判断逻辑自然永远不会执行。
可落地的解决方案

1. 主动做可用内存阈值检查,提前预警

不需要修改系统全局配置,只需要在每次完成内存写入(也就是触发真实物理页分配)后,主动读取系统真实可用内存值,达到预警阈值时就给用户提示,不等内核触发OOM。
系统真实可用内存可以直接从/proc/meminfo的MemAvailable字段读取,这个值是内核统计的、真正可以分配给用户进程的内存总量,比单纯看MemFree准确得多。
可以参考如下改造后的代码逻辑:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>

#define ONEGB (1UL << 30)
// 自定义预警阈值,比如剩余2GB可用时就触发警告
#define WARN_THRESHOLD (2UL * ONEGB)

// 读取系统当前真实可用内存,单位字节
long get_system_available_mem() {
    FILE *meminfo = fopen("/proc/meminfo", "r");
    if (!meminfo) return -1;
    long avail_kb = 0;
    char line_buf[256];
    while (fgets(line_buf, sizeof(line_buf), meminfo)) {
        if (sscanf(line_buf, "MemAvailable: %ld kB", &avail_kb) == 1) {
            break;
        }
    }
    fclose(meminfo);
    return avail_kb * 1024; // 转换为字节单位
}

int main() {
    int count = 0;
    while (1) {
        int *p = malloc(ONEGB);
        if (p == NULL) {
            printf("There is not much memory left after %d GB\n", count);
            return 0;
        }
        // 写入内容触发真实物理页分配
        memset(p, 1, ONEGB);
        fprintf(stderr, "got %d GB\n", ++count);

        // 每次分配完成后主动检查可用内存
        long avail_mem = get_system_available_mem();
        if (avail_mem > 0 && avail_mem < WARN_THRESHOLD) {
            printf("Warning: Only %ld MB system memory left, process may be terminated soon\n",
                   avail_mem / (1024 * 1024));
            // 这里可以做资源清理、任务持久化等后续操作
            free(p);
            return 0;
        }
    }
}

2. 绕过延迟分配,让内核在申请时就校验内存是否充足

大内存申请(比如1GB这种量级)在glibc的实现里本来就是走mmap系统调用的,默认是延迟分配(lazy allocation)。你可以直接改用带MAP_POPULATE标志的mmap申请内存,这个标志会让内核在调用mmap时就直接完成所有内存页的映射,同时校验是否有足够的物理内存+交换空间支撑这段分配:

  • 如果资源不足,mmap会直接返回MAP_FAILED,不会等后续写入内存时才触发OOM
  • 原来的malloc(ONEGB)代码可以直接替换为:
#include <sys/mman.h>
// 申请1G内存,立刻完成页映射和资源校验
void *p = mmap(
    NULL, 
    ONEGB, 
    PROT_READ | PROT_WRITE, 
    MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE, 
    -1, 
    0
);
if (p == MAP_FAILED) {
    printf("There is not much memory left after %d GB\n", count);
    return 0;
}

这个方法的好处是完全不需要依赖系统全局参数修改,只要拿到内存就说明内核已经确认资源足够,不会出现申请成功、写入时被OOM杀死的问题。

3. 调整全局Overcommit策略(需root权限)

如果允许修改系统配置,可以把Overcommit模式调整为严格校验模式:

  • 执行echo 2 > /proc/sys/vm/overcommit_memory 切换为严格内存校验模式
  • 根据服务器实际内存设置合理的overcommit_ratio,比如物理内存32G、交换空间4G的场景,可以设置echo 90 > /proc/sys/vm/overcommit_ratio

调整后内核就不会允许进程申请超过可支配内存上限的空间,内存不足时malloc会直接返回NULL,不会出现延迟触发OOM的问题。但这个修改是全局生效的,会影响服务器上所有进程的内存分配行为,非必要不建议使用。

关键注意事项
  • OOM Killer发送的SIGKILL信号是不可捕获、不可阻塞、不可忽略的,进程收到这个信号根本没有机会执行自定义逻辑,所有预警、清理操作必须在OOM触发前完成。
  • 不要用MemFree字段做内存判断,这个值只统计完全未被占用的空闲页,不包含可以回收的内核缓存、slab缓存,会导致判断结果和实际可分配内存偏差很大,必须使用MemAvailable字段。
  • 带MAP_POPULATE的mmap分配速度会比默认的延迟分配稍慢,因为内核需要一次性完成所有页的映射,对于GB级别的大块内存任务,这点速度差异完全在可接受范围内。

内容的提问来源于stack exchange,提问作者Shahin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.11.10 12:27:03