☰
银河麒麟V10内存不释放怎么办?定时清理机制与运维实践指南
2026/10/15 6:58:01 网站建设 项目流程

简介:一套基于Shell脚本的定时清理运维工具包,专门针对银河麒麟V10系统内存不释放问题而整理。压缩包内共3个文件,包含两个Shell脚本与一份配置说明文本:脚本用于定时监测内存占用并触发释放操作,文本对参数调整和部署要点进行说明,整体仅2KB,轻量易用。面向负责国产化服务器日常维护的运维工程师,已有780人学习下载。借助该工具包,读者可掌握内存监控与定时任务的实际写法,了解如何在银河麒麟V10环境中结合cron或systemd timer安排脚本执行,并能根据说明文件自行调整阈值与频率,有效降低手动干预成本,缓解内存泄漏导致的可用内存逐步下降问题,提升系统长期运行的稳定性。

1. 银河麒麟V10内存不释放:定时清理才是运维的后悔药

接手过国产化服务器运维的人,多半都见过这种场面:某台银河麒麟V10机器,跑着跑着free -h里 available 跌破 10%,但 top 一看,真正吃内存的进程根本没几个。swap 被撑满,业务接口响应开始变慢,重启又不敢随便重启——这就是所谓“内存不释放”的典型症状。银河麒麟V10 基于 Linux 内核,内存管理机制沿用了 Linux 的 page cache / buffer 逻辑,但在某些业务负载下,回收策略表现得比 CentOS 更“迟钝”。本文直接给出一个可落地的定时解决方案:用脚本定期触发内存回收,配合 crontab 或 systemd timer 实现无人值守。全文覆盖原理、脚本参数、踩坑记录,最后附上验证手段,适合所有跑在麒麟 V10 上的生产服务器运维直接抄作业。

2. 先搞清楚一件事:内存到底是被谁“吃”掉的

2.1 麒麟V10 的内存回收机制与 CentOS 的差异

银河麒麟V10 的内核版本整体较新,但默认的vm.swappiness、vm.vfs_cache_pressure等参数和 CentOS 7/8 系比较接近。问题恰恰出在这里——麒麟 V10 在硬件兼容层做了不少适配,尤其是针对国产芯片(比如 ARM 架构的型号)做了内存管理上的调整,这让它在某些读写频繁的业务下,page cache 增长速度比预期快得多。

我排查过的一台机器是这样的:跑着一个 Java 服务,外加 MySQL,内存一共 64G。连续跑三天后,free -h显示 used 57G,但把 Java 进程和 MySQL 的 RSS 全加起来,只有 23G。剩下 34G 全是 cache/buffer。这时候界面上显示“内存使用率 92%”,实际上业务进程并不缺内存,但监控告警已经响了。如果不处理,swap 分区一旦被大量使用,IO 延迟就会开始拖垮业务。

Linux 设计上本来就允许 cache 占满空闲内存,因为它能在文件读写时显著提升性能。但“允许”不等于“必须释放”——触发回收的条件是内存水位线达到watermark的某个阈值,或者显式写入/proc/sys/vm/drop_caches。麒麟 V10 默认的vm.extra_free_kbytes和vm.min_free_kbytes在一些版本上被调得偏保守,导致回收不够积极。

2.2 drop_caches 的三个层级:1、2、3 到底选哪个

内核提供了手动释放缓存的口子,就是往/proc/sys/vm/drop_caches写值。这个值分三层:

  • 1:释放 page cache(页缓存),这是最常用的,不影响 dentry 和 inode 缓存;
  • 2:释放 dentry(目录项缓存)和 inode(索引节点缓存),不碰 page cache;
  • 3:同时释放 page cache、dentry、inode,相当于 1 和 2 都做。

生产环境里,我一般只选 3。原因很简单:我们做定时清理的目的是“把可用内存还给系统”,不是精细化管理某一类缓存。分开释放还得写两次命令,而且执行完效果还不如一次性释放干净。

但这里有一个容易踩的坑:drop_caches不能替代业务进程的内存管理。如果内存是被 Java 堆或者 MySQL buffer pool 占掉的,写多少遍drop_caches都没用。定时清理只解决“内核态缓存占用过高”这一种场景。判断标准很简单——执行sync; echo 3 > /proc/sys/vm/drop_caches之后,free -h的 available 如果回升明显,说明确实是 cache 占用;如果没变化,说明内存被业务进程拿了,得从应用层优化。

2.3 为什么需要“定时”而不是“手动执行一次”

手动执行当然可以,但运维的现实是:你永远不知道什么时候内存会突然飙高。凌晨三点的业务夜间批量任务、每周固定的备份脚本、月底报表导出,这些都会触发大量文件读写,瞬间把 cache 撑大。定了时的好处是把“发现问题-处理问题”变成“预防问题”。

常见的定时策略有两种:第一种是固定间隔,比如每 30 分钟执行一次;第二种是结合监控,在内存超过某个阈值时触发。第二种更科学,但实现成本高一些。本文先给固定间隔的方案,然后在第 6 章补充一个更稳的变体——加了日志和监控联动。

提示:无论用哪种方式,定时释放 cache 只适合 cache/dentry 占用高的机器。如果业务本身就缺内存,定时清理解决不了问题,必须先扩容或优化应用。

3. 写一个可落地的内存清理脚本:从裸命令到带日志的完整版

3.1 原始版本:三行命了解决“当前缓存”

先看最基础的做法。SSH 登录服务器,执行以下三行命令:

sync echo 3 > /proc/sys/vm/drop_caches free -h

第一行sync的作用是把文件系统缓冲区里的脏数据强制写回磁盘。这一步不能省,否则直接执行echo 3可能导致尚未落盘的写缓存被强制丢弃,虽然概率很低,但后果很严重。第二行写入值 3,即同时释放 page cache、dentry 和 inode。第三行打印内存状态,确认效果。

这个版本的问题很明显:必须人工登录执行,而且没有任何输出记录。如果深夜内存告警,你根本来不及跑这三行命令。

3.2 进阶版:带阈值判断、日志和防止重复执行的完整脚本

生产环境里,我习惯把它写成一个可执行的 shell 脚本,放在/usr/local/bin/下。完整脚本如下:

#!/bin/bash # description: Release memory cache on Kylin V10 when available memory is low # usage: /usr/local/bin/release_mem.sh MEM_THRESHOLD=2048 # free memory threshold in MB, trigger release when below this LOG_FILE="/var/log/mem_release.log" LOCK_FILE="/tmp/mem_release.lock" # check if another instance is running if [ -e "$LOCK_FILE" ]; then echo "$(date '+%Y-%m-%d %H:%M:%S') [SKIP] lock file exists, another process may be running" >> "$LOG_FILE" exit 0 fi # create lock file touch "$LOCK_FILE" # get available memory in MB AVAILABLE_MEM=$(free -m | awk '/^Mem:/{print $7}') echo "$(date '+%Y-%m-%d %H:%M:%S') [INFO] available memory: ${AVAILABLE_MEM}MB, threshold: ${MEM_THRESHOLD}MB" >> "$LOG_FILE" if [ "$AVAILABLE_MEM" -lt "$MEM_THRESHOLD" ]; then # sync dirty buffers to disk first sync # drop page cache, dentry and inode cache echo 3 > /proc/sys/vm/drop_caches echo "$(date '+%Y-%m-%d %H:%M:%S') [INFO] memory cache released, available now: $(free -m | awk '/^Mem:/{print $7}')MB" >> "$LOG_FILE" else echo "$(date '+%Y-%m-%d %H:%M:%S') [INFO] available memory is sufficient, no action taken" >> "$LOG_FILE" fi # remove lock file rm -f "$LOCK_FILE"

逻辑拆解如下:

阈值判断:MEM_THRESHOLD=2048表示当可用内存低于 2048MB(2GB)时才执行释放。这个参数是整段脚本的核心,设置多少直接决定脚本触发的频繁程度。如果你机器的总内存是 32G,建议设为 4096;如果是 128G 的大内存机器,可以设到 8192 甚至更高。原则是:可用内存低于总内存的 10% 左右时触发清理,保证业务有余量。

锁文件机制:LOCK_FILE="/tmp/mem_release.lock"用来防止脚本并发执行。crontab 最小粒度是分钟,正常情况下同一个脚本一分钟内跑不完。但万事怕万一——如果日志写入卡在磁盘 IO 上,或者sync因为大量脏页写回耗时过长,下一轮 crontab 触发时可能会重复执行。锁文件能在这种场景下直接跳过,避免重复释放导致 IO 抖动。

日志记录:LOG_FILE="/var/log/mem_release.log"完整记录每次执行的时间、可用内存、触发/跳过状态、释放前后的对比。这个日志是事后排查的唯一依据。我在第 6 章会演示怎么用它做趋势分析。

执行权限:别忘了chmod +x /usr/local/bin/release_mem.sh,否则 crontab 执行时会报 permission denied。

3.3 先手工跑两轮,确认脚本行为符合预期

脚本写完不能直接挂 crontab,先手工执行两遍,确认输出符合预期:

# 先看当前内存 free -m # 手工执行第一遍(大概率会触发释放) /usr/local/bin/release_mem.sh # 立刻再执行第二遍(验证锁文件和阈值逻辑) /usr/local/bin/release_mem.sh # 查看日志 cat /var/log/mem_release.log

第一遍执行时,如果 available 低于阈值,日志里会出现memory cache released的字样;第二遍因为可用内存已经被释放,大概率走available memory is sufficient分支。这两行日志的区别就说明逻辑链路是通的。

提示:手工执行时如果发现echo 3 > /proc/sys/vm/drop_caches提示 Permission denied,先检查当前用户是否有 root 权限。这个操作必须有 root 权限,普通用户执行不了。

4. crontab 定时任务配置:五分钟还是半小时,看业务脸色

4.1 crontab 的写法和参数调整

脚本准备好了,接下来就是把它挂到系统定时任务里。用 root 执行:

crontab -e

写入:

*/5 * * * * /usr/local/bin/release_mem.sh

这一行的含义是:每 5 分钟执行一次内存清理脚本。*/5是分钟段的步进写法,等价于0,5,10,15,20,25,30,35,40,45,50,55。建议保留默认的 root crontab,不要单独创建普通用户的定时任务,否则可能出现权限不足或者 PATH 环境变量不一致的问题。

间隔选多少,取决于你的业务负载。经验上分几档:

  • 五分钟档:适合业务以大量文件读写为主、内存波动大的机器,比如跑着批量数据处理任务的服务器;
  • 半小时档:适合内存缓慢增长、一两天才需要清理一次的机器,比如普通的 Web 应用服务器;
  • 一小时档:适合内存相对稳定的机器,定时清理更像一个兜底保险,防止突发性 cache 增长。

我的习惯是从半小时开始,观察一周日志。如果触发频率太高(一晚上触发十几次),就把间隔调长;如果触发频率太低(一周才触发一两次),说明阈值设置得太保守,可以适当提高MEM_THRESHOLD。调参需要日志数据的支撑,这正是上面那个脚本保留日志的意义。

4.2 crontab 环境变量坑:为什么脚本在终端能跑、定时就不行

这个坑几乎每个运维都遇到过:脚本在终端手工执行一切正常,但挂到 crontab 里,要么日志文件没生成,要么命令报command not found。原因在于 crontab 执行时的 PATH 环境变量极简,默认只有/usr/bin:/bin,而你系统里的一些命令可能装在/usr/local/bin下(比如自己编译安装的工具)。

解决方法有两种。第一种是在脚本开头显式声明 PATH:

#!/bin/bash export PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

第二种是在 crontab 里给脚本加绝对路径,脚本内部要调用的命令也全写绝对路径。我推荐第一种——声明 PATH 一劳永逸,下次往脚本里加新命令时不用逐个检查路径。

4.3 crontab 的日志重定向问题

默认情况下,crontab 执行脚本时,如果脚本有 stdout/stderr 输出,会以邮件形式发到 root 的邮箱。大部分服务器根本没配邮件服务,这个输出就会堆积在/var/spool/mail/root文件里,时间长了占磁盘空间。

所以 crontab 里挂脚本,输出重定向是标配:

*/30 * * * * /usr/local/bin/release_mem.sh >/dev/null 2>&1

>/dev/null把标准输出丢弃,2>&1把标准错误重定向到标准输出,合起来就是所有输出都丢进黑洞。脚本内部已经有完整的日志记录,外部输出不需要保留。

如果你希望脚本执行出问题时有感知,可以把重定向改成写进文件:

*/30 * * * * /usr/local/bin/release_mem.sh >>/var/log/release_mem_cron.log 2>&1

这样 crontab 本身的报错会追加到这个文件,但代价是这个文件会持续增长。生产环境我一般用前者(丢弃外部输出),依靠脚本内部的/var/log/mem_release.log做追踪。

5. 避坑指南:清理脚本在麒麟V10上的四个典型翻车现场

5.1 现象一:释放后内存反而更紧张了,swap 使用量不降反升

现象:执行echo 3 > /proc/sys/vm/drop_caches后,free -h显示 available 确实增加了,但swap -h显示的 swap used 数值没有下降,甚至略有上涨。业务侧反馈磁盘 IO 出现短暂飙升。

原因:drop_caches释放的是只读缓存,它不参与写回,所以释放动作本身很快。但释放出的空闲内存会被内核用于“写回脏页”的缓冲——如果之前sync没执行干净,或者系统的脏页比例(vm.dirty_ratio)设置较高,释放完的瞬间内核会拿这批空闲内存继续做写缓存,IO 反而被推高。另一个原因:swap 里的内容不会被drop_caches被动回收,swap 只能靠内存压力触发换出,所以 swap used 短时间不会降。

解决:脚本里sync放在echo 3前面且必须单独一行,确保大部分脏页在释放缓存前已经落盘。swap 使用量短期不降是正常的,不要反复执行脚本去“挤”swap——这只会加剧 IO 抖动。如果业务对 swap 很敏感,可以在释放后加一句swapoff -a && swapon -a,但这招在集群环境要慎重,建议只在单机排查时用。

5.2 现象二:进程的 RSS 明明不高,但 top 显示内存占用超过 90%

现象:某天业务告警“内存不足”,登录服务器一看,free -hused 84G(总内存 64G,显然不对),top 按内存排序后所有进程 RSS 加起来也就 20G 出头。

原因:free命令的 used 列包含 page cache。Linux 语义里,page cache 属于“可回收内存”,但它在free -h的 used 里是算进去的。监控平台如果直接取 used 作为内存使用率,就会把缓存当成实打实的占用,cpu 监控曲线看起来就像“内存泄漏”。麒麟 V10 部分版本的监控组件确实有这样的问题。

解决:看可用内存不要看 used,要看available列。脚本里的阈值判断用的就是free -m的第七列(available),不是第二列(used)。如果你的监控脚本还在用旧习惯取 used 做判断,改成取 available 是第一步。

5.3 现象三:crontab 里写了脚本,但日志文件永远不更新

现象:crontab -e配置好了,等了半小时,/var/log/mem_release.log文件的时间戳还是老的,甚至文件都不存在。

原因:排查顺序有几个方向。第一个是 crontab 服务本身没起来——麒麟 V10 上 crond 服务默认是开的,但如果你做过服务裁剪,这个可能被关掉。第二个是脚本执行报错,卡在 PATH 上(第 4.2 节已经说过)。第三个是脚本根本没有执行权限,chmod +x没做。

解决:按顺序排查。先systemctl status crond确认服务状态;再grep CRON /var/log/cron看 crontab 是否真的触发;最后手工执行一遍脚本确认本身能跑通。如果前两步正常,把 crontab 里的输出重定向暂时去掉,直接看报错内容,这样定位最快。

5.4 现象四:释放内存后业务反而变慢了,现象持续了好几分钟

现象:定时任务执行后,available 内存确实涨了,但业务接口响应时间在接下来的几分钟内明显变长。

原因:cache 被清掉后,业务进程再次访问相同文件时需要从磁盘重新读入 page cache,这段时间内文件 IO 是直线打到磁盘的。如果业务是频繁读写同一批文件的类型(比如数据库的 redo log、临时表空间),清理缓存等于把这些热数据“赶出”了内存,必然带来性能回退。

解决:优化清理策略——不是清得越频繁越好。把阈值降低,只有在 available 快耗尽时才触发;同时拉长间隔,避免把热 cache 反复清掉。针对数据库服务器,我一般会禁止清理,或者把MEM_THRESHOLD压到 512MB,只在极端情况兜底。如果你希望清理后热数据能快速重建,可以在脚本里加一句vmtouch预热(需要额外安装),但大多数场景没必要。

提示:多事务数据库服务器(如 MySQL、PostgreSQL)不建议跑这个脚本。数据库有自己的 buffer pool 管理机制,page cache 对它的性能影响很大,清理反而帮倒忙。

6. 进阶验证:从日志里读出趋势,把清理脚本升级成“带预警”的监控联动

脚本能跑只是第一步,真正值钱的是从日志里看到内存行为规律。我目前的习惯是每周看一次/var/log/mem_release.log,但更推荐你写一个简单的 awk 分析命令:

awk '/\[INFO\] available memory/{print $1, $2, $NF}' /var/log/mem_release.log | tail -20

这条命令把每次执行的日期时间和可用内存数值过滤出来,最近 20 条记录一目了然。如果发现触发频繁度在上升——比如上周每天触发两次,这周每天触发五次——那不是 cache 的问题了,可能是业务程序在泄漏内存,应该用ps aux --sort=-rss或pmap盯具体进程。

除了人工看日志,还可以把脚本升级成“带预警”的版本,核心动作是在释放前后各写一行日志,然后结合守护进程做阈值告警。下面这段是在原脚本基础上加的预警片段:

# in script: add alert when memory is critically low if [ "$AVAILABLE_MEM" -lt 1024 ]; then # send warning message to alert endpoint logger -t memory-alert "available memory below 1GB: ${AVAILABLE_MEM}MB" # you can also call a webhook or write to a /tmp file for monitoring agents to pick up echo "critical: available ${AVAILABLE_MEM}MB at $(date '+%Y-%m-%d %H:%M:%S')" > /tmp/mem_critical_flag fi

这段不是在释放内存,而是在内存即将耗尽之前留下一个“痕迹”。logger -t会把消息送进 syslog,你的监控系统只要采集 syslog 就能抓到这条预警;写/tmp/mem_critical_flag文件则是给 node_exporter 这类采集器用的,比如让它在指标里暴露一个文件存在性标识。

如果机器上装了 node_exporter,配合 textfile collector,可以绕开任何 API 直接让 Prometheus 抓到内存压力信号。做法是让脚本把当前 available 内存写入指定文本文件,node_exporter 再把这个文件转成监控指标。这套方案在麒麟 V10 上测试过,完全兼容,因为它本质上就是一个普通文件写入,不依赖任何麒麟特有的 SDK。监控告警的链路搭好之后,定时清理脚本的角色就从“救火队员”变成了“缓冲垫”——真正兜底的是告警,定时清理只是在告警触发之前把水位压住而已。

回到标题里的需求,“定时解决内存不释放”这件事,本质不是迷信某个神奇的清理命令,而是建立一套“阈值判断 + 自动执行 + 日志追踪 + 告警预警”的运维闭环。每一次内存告警都要当成一次复盘机会,拿着日志曲线看规律,判断这次的缓存增长是业务特性还是代码缺陷,然后反过来调脚本参数——间隔是拉长还是缩短,阈值是抬高还是压低。从那以后,我每次在麒麟 V10 上部署一台新服务器,都会先把这套脚本和日志路径建好,再让业务上线,宁可先跑三天空转,也要拿到一份干净的内存基线数据。这年头内存翻车容易,但翻车之后能拿出日志和趋势图来复盘,就是老手和新手的区别。希望这些内容能帮你在自己的机器上少走两趟弯路。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询