现象

工作中线上机器使用的是物理机,发现当程序使用内存到一定量,明明 free 还有几十G,但程序已经崩溃,查看异常信息提示 no space,这种是什么情况?

free -mh 查看内存使用情况,free 剩余几十G,但 available 只剩余几个G

原因分析

难道应用程序只能申请到最大 available 内存?

写程序脚本尝试动态占用内存,发现分配接近 available 总量时已经提示申请内存失败(体感 available 即为应用程序可用剩余最大内存),感觉应该就是如此了。

简单demo如下:

#include

#include

#include

int main() {

size_t maxMemoryInGB;

std::cout << "请输入您想要申请的最大内存(GB):";

std::cin >> maxMemoryInGB;

const size_t oneGB = 1073741824; // 1GB in bytes

size_t totalAllocated = 0;

size_t maxMemoryInBytes = maxMemoryInGB * oneGB;

void* ptr;

while (totalAllocated < maxMemoryInBytes) {

ptr = malloc(oneGB);

if (ptr == nullptr) {

std::cout << "内存申请失败,总共申请的内存大小为:" << (totalAllocated / oneGB) << "GB" << std::endl;

break;

}

totalAllocated += oneGB;

// 为分配的内存赋值,这里简单地将所有字节设为某个非零值,例如1

memset(ptr, 1, oneGB);

}

// 释放之前分配的内存

while (totalAllocated > 0) {

free(ptr);

totalAllocated -= oneGB;

ptr = nullptr; // 重置指针,避免悬挂指针

}

return 0;

}

根据机器内核信息 查看 available 计算逻辑

查阅资料,Linux 内核负责统计内存使用量并暴露在 /proc 伪文件系统中,路径是 /proc/meminfo 。一般而言,需要重点关注的指标如下: 内存使用量指标(字节):

total 表示 物理内存总量 ,单位为 字节 ,对应 /proc/meminfo 的 MemTotal 字段。

free 表示 空闲内存量 ,单位为 字节 , 对应 /proc/meminfo 的 MemFree 字段。

buffers 表示 内核缓冲区 ,单位为 字节 ,对应 /proc/meminfo 的 Buffers 字段。

cached 表示 文件缓冲页 ,单位为 字节 ,对应 /proc/meminfo 的 Cached 字段。

slab 表示 内核 slab 数据结构 ,单位为 字节 ,对应 /proc/meminfo 的 Slab 字段。

cache 与 free 命令中的 cache 相同,即 cached 以及 slab 之和:cache = cached + slab

g_free 表示 广义空闲内存 ( generalized free ),单位为 字节 ,计算方式如下:g_free = free + buffers + cache buffers 和 cache 是系统为了提升性能而使用的缓存,内存紧张时可随时回收另做它用。因此,这部分内存在某种意义上可以认为是空闲的,这就是 广义空闲内存 的由来。

used 表示 已用内存 ,单位为 字节 ,计算方式如下:used = total - g_free = total - free - buffers - cache

active 表示 活跃内存 ,单位为 字节 ,对应 /proc/meminfo 的 Active 字段。 活跃内存 是指最近经常访问的内存,通常不会被重新分配,除非非常必要。

inactive 表示 非活跃内存 ,单位为 字节 ,对应 /proc/meminfo 的 Inactive 字段。 非活跃内存 是指最近较少访问的内存,需要新分配内存时,这部分优先选择。

available3.14 内核版本开始提供在 /proc/meminfo 的 MemAvailable 字段,available 表示 可用内存 ,单位为 字节。

计算公式:

available = free_pages - total_reserved + pagecache + SReclaimable

# 计算 wmark_low

wmark_low = awk '/min/ {sum += $2} END {print sum * 4}' /proc/zoneinfo

# 计算空闲页 free_pages

free_pages = awk '/free / {sum += $3} END {print sum * 4}' /proc/zoneinfo

# 计算保留内存

total_reserved = Σ(min((max(lowmem) + high_watermark), managed))

max(lowmem) = awk '/protection/ {gsub(/[\(\),]/," "); print $5}' /proc/zoneinfo

high_watermark = awk '/high / {print $2}' /proc/zoneinfo

managed = awk '/managed/ {print $2}' /proc/zoneinfo

paste <(paste <(awk '/protection/ {gsub(/[\(\),]/," "); print $5}' /proc/zoneinfo) <(awk '/high / {print $2}' /proc/zoneinfo) | awk '{print $1+$2}') <(awk '/managed/ {print $2}' /proc/zoneinfo) | awk '{total_reserved += ($1 > $2 ? $2 : $1)} END {print total_reserved * 4}'

# 计算 pagecache

pagecache = active file + inactive file

echo $(($(awk '/nr_inactive_file/{sum += $2} END {print sum * 4}' /proc/zoneinfo) + $(awk '/nr_active_file/{sum += $2} END {print sum * 4}' /proc/zoneinfo)))

# pagecache -= min(pagecache / 2, wmark_low),并不是所有的 pagecache 都被认为是可用的:

pagecache -= min(pagecache / 2, wmark_low),并不是所有的 pagecache 都被认为是可用的:

echo $(($(echo $(($(awk '/nr_inactive_file/{sum += $2} END {print sum * 4}' /proc/zoneinfo) + $(awk '/nr_active_file/{sum += $2} END {print sum * 4}' /proc/zoneinfo)))) - $(paste <(awk '/min/ {sum += $2} END {print sum * 4}' /proc/zoneinfo) <(echo $(($(awk '/nr_inactive_file/{sum += $2} END {print sum * 4}' /proc/zoneinfo) + $(awk '/nr_active_file/{sum += $2} END {print sum * 4}' /proc/zoneinfo)))) | awk '{min = ($1 > $2/2 ? $2 : $1); print min}')))

# 计算 SReclaimable

awk '/nr_slab_reclaimable/ {sum += $2} END {print sum * 4}' /proc/zoneinfo

# SReclaimable -= min(SReclaimable/2, wmark_low),和 pagecache 相似,不能全用。

echo $(($(awk '/nr_slab_reclaimable/ {sum += $2} END {print sum * 4}' /proc/zoneinfo) - $(paste <(awk '/nr_slab_reclaimable/ {sum += $2} END {print sum * 4}' /proc/zoneinfo) <(awk '/min/ {sum += $2} END {print sum * 4}' /proc/zoneinfo) | awk '{min = ($1 > $2/2 ? $2 : $1); print min}')))

# available = free_pages - total_reserved + pagecache + SReclaimable

echo $((($(awk '/free / {sum += $3} END {print sum * 4}' /proc/zoneinfo) - $(paste <(paste <(awk '/protection/ {gsub(/[\(\),]/," "); print $5}' /proc/zoneinfo) <(awk '/high / {print $2}' /proc/zoneinfo) | awk '{print $1+$2}') <(awk '/managed/ {print $2}' /proc/zoneinfo) | awk '{total_reserved += ($1 > $2 ? $2 : $1)} END {print total_reserved * 4}') + $(echo $(($(echo $(($(awk '/nr_inactive_file/{sum += $2} END {print sum * 4}' /proc/zoneinfo) + $(awk '/nr_active_file/{sum += $2} END {print sum * 4}' /proc/zoneinfo)))) - $(paste <(awk '/min/ {sum += $2} END {print sum * 4}' /proc/zoneinfo) <(echo $(($(awk '/nr_inactive_file/{sum += $2} END {print sum * 4}' /proc/zoneinfo) + $(awk '/nr_active_file/{sum += $2} END {print sum * 4}' /proc/zoneinfo)))) | awk '{min = ($1 > $2/2 ? $2 : $1); print min}')))) + $(echo $(($(awk '/nr_slab_reclaimable/ {sum += $2} END {print sum * 4}' /proc/zoneinfo) - $(paste <(awk '/nr_slab_reclaimable/ {sum += $2} END {print sum * 4}' /proc/zoneinfo) <(awk '/min/ {sum += $2} END {print sum * 4}' /proc/zoneinfo) | awk '{min = ($1 > $2/2 ? $2 : $1); print min}')))))/1024))

调大 available 占用,尝试恢复服务

根据调研信息,available 计算会根据系统配置参数及使用情况动态计算,那就看哪些内存必须占用哪些参数可以动态调整适当增大 available 量了。

cat /proc/sys/vm/min_free_kbytes 结果为8G (min_free_kbytes 保证系统间可用的最小 KB 数。 这个值可用来计算每个低内存区的水印值,然后为其大小按比例分配保留的可用页。)

cat /proc/zoneinfo|grep -i low 发现 low 总共有20G+, 这个可以考虑调整

如何调整这个值?以及这个是什么含义?可以看下这个文章:

Linux内核调整watermark_scale_factor以缓解direct reclaim

那么直接调整 watermark_scale_factor 影响这个low值

sudo bash -c "echo 10>/proc/sys/vm/watermark_scale_factor"

调整完后再通过 free 查看 available 可用内存直接增加几十G,服务也恢复正常

结束了?available 就是程序剩余可用内存?

available 就是留给应用程序可用的内存了,但感觉是不是也太苛刻?难道就不能动态多申请点?程序猿只要深究总能找出点不一样的东西

从排查问题上看 available 就是留给应用程序可用最大内存,原因也跟系统配置有关,机器overcommit_memory 配置为 0,这个是怎么影响内存申请呢?

可用看下这个文章:

查看系统设置的overcommit参数置:

cat /proc/sys/vm/overcommit_memory

通过overcommit_memory设置应用程序使用虚拟内存的策略,支持的策略如下:

#define OVERCOMMIT_GUESS 0

#define OVERCOMMIT_ALWAYS 1

#define OVERCOMMIT_NEVER 2

OVERCOMMIT_GUESS 表示根据系统当前可用page frame进行判断,如果可用page frame大于申请的虚拟内存,则允许申请虚拟内存;OVERCOMMIT_ALWAYS 表示总是允许申请虚拟内存,没有任何限制;OVERCOMMIT_NEVER 表示不允许超过系统设置的虚拟内存限制。

整个问题排查到此