机器中明明 free 还有很大空间,但为什么程序运行提示内存不足? available 是不是当前程序可以使用的剩余内存总量?
工作中线上机器使用的是物理机,发现当程序使用内存到一定量,明明 free 还有几十G,但程序已经崩溃,查看异常信息提示 no space,这种是什么情况?free -mh 查看内存使用情况,free 剩余几十G,但 available 只剩余几个G。
现象
工作中线上机器使用的是物理机,发现当程序使用内存到一定量,明明 free 还有几十G,但程序已经崩溃,查看异常信息提示 no space,这种是什么情况?
free -mh 查看内存使用情况,free 剩余几十G,但 available 只剩余几个G
原因分析
难道应用程序只能申请到最大 available 内存?
写程序脚本尝试动态占用内存,发现分配接近 available 总量时已经提示申请内存失败(体感 available 即为应用程序可用剩余最大内存),感觉应该就是如此了。
简单demo如下:
#include <iostream>
#include <cstdlib>
#include <cstring>
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 表示不允许超过系统设置的虚拟内存限制。
整个问题排查到此
更多推荐
所有评论(0)