简介:面向银河麒麟V10服务器运维人员的实用资源包,针对系统长期运行中出现的内存不释放(内存泄漏)问题,提供定时清理与监控的脚本化解决方案。包内共3个文件,包含2个Shell脚本和1个配置说明文档:freemem.sh负责按周期执行内存回收逻辑,task.sh用于配合系统定时任务调度,txt配置说明则对脚本参数、部署方式与注意事项做了补充说明。压缩包整体仅2KB,轻量易用,适合已具备基础运维知识的读者快速部署。已有773人学习下载,说明该方案在同类运维场景中具有一定参考价值。通过这份资源,读者可以掌握定时触发内存释放的实现思路,理解Shell脚本结合cron定时任务处理内存异常的基本方法,并直接复用或调整脚本以适应自身生产环境,提升银河麒麟V10系统的长期稳定性。
1. 银河麒麟V10内存不释放:先分清真增长还是假增长再动手
一台跑着银河麒麟V10的服务器,开机跑了两周, free 里 used 一路爬升到 95% 以上,业务响应开始飘红,重启两三天又原样,这种情况在国产化迁移后特别常见。所谓“内存不释放”,大多数时候不是进程泄漏,而是 Linux 把空闲内存拿去做页缓存,又没在业务需要时及时交还——表现出来就是内存膨胀、使用率持续走高。这篇笔记要给的是「定时加水位判断」的回收办法:先判断该不该清,再由 cron 定时清,最后还要能验证清完到底有没有真效果。这套做法适合跑 Web、容器、Java 服务,内存余量又不多的麒麟V10服务器,也是我们迁移后最先沉淀下来的运维套路之一。
2. 先用 free 和 /proc/meminfo 定位:内存真紧张还是缓存没交还
2.1 用 free -m 看内存:buff/cache 才是大头
很多运维拿到一台麒麟V10,第一件事就是敲free -h,看到 used 高就紧张。实际上 free 输出里只有 available 能代表“新开一个进程还能拿到多少内存”,这一列从 Linux 3.14 以后才加入,麒麟V10基于 4.19 内核,自然带。先看基础命令:
free -m # total used free shared buff/cache available # Mem: 15870 8890 312 125 7660 7159 # Swap: 8191 0 8191注意这个输出:used 约 8.7G,available 约 7.1G,看起来压力并不大。但很多时候你看到的是 used 14.7G、available 只剩 1.1G 的样子——此时再看 buff/cache 还有 7 个多 G,说明相当一部分内存被内核拿去做页缓存了。业务真正的独占内存并没有 used 显示得那么大,这就是典型的“假性不释放”。
这里有一条我常用的判断口径:available 持续低于 total 的 20%,且业务出现卡顿,才有干预的必要;如果只是 used 高、available 还有余量,完全不用管,缓存多恰恰说明内存被有效利用了。另外提醒一句,别用 free 里的 free 列做监控指标,那列在内存越大、缓存越多的情况下越有误导性。
2.2 从 /proc/meminfo 看脏页水位:该不该清的判断标准
free 只能看总量,想进一步判断“缓存里有没有堆积太多没刷盘的脏数据”,要看 /proc/meminfo:
grep -E '^Mem(Available|Free):|^Dirty:|^Writeback:|^Cached:' /proc/meminfo输出类似这样:
MemAvailable: 6432060 kB MemFree: 319488 kB Cached: 8847360 kB Dirty: 1894400 kB Writeback: 30720 kB各字段的实践含义是:Dirty 表示内存里改过、还没写回磁盘的页,空闲期正常的服务器这个值应该很小;Writeback 是正在写回的页,持续很大说明磁盘已经跟不上内存的写入速度。Cached 里包含 tmpfs 这类不可随意回收的部分,所以不能只看 Cached 判断可回收量。
麒麟V10 的 /proc/meminfo 字段和上游一致,monitor 脚本可以直接按这个口径取数。我一般会把 Dirty 加进监控:当它连续 5 分钟超过物理内存的 10%,说明后台回写线程在堆积,这时候才值得用清理脚本去触发回写。
提示:判断清理必要性,优先看 MemAvailable 和 Dirty,不要盯着 used 列。available 低才是真紧张,dirty 高才是该回写。
2.3 用 vmstat 观察换页:si/so 才是真正的性能信号
内存有没有问题,老道的做法是看内存是否在持续换页:
vmstat 1 5 # procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- # r b swpd free buff cache si so bi bo in cs us sy id wa st # 1 0 24512 5120 12401 885101 0 0 12211 14220 1234 3312 8 5 86 0 0si/so 两列是 swap-in 和 swap-out,如果它们持续非零且波动大,说明内存压力已经传导到 swap 分区,进程在被换出换入,这通常伴随高 IO wait 和响应延迟。反过来,如果 si/so 长期为 0,就算 free 看着低,也只是缓存策略问题,内存并没有真正成为瓶颈。
很多用户在麒麟V10 上“内存还是高”但机器又不卡,多半属于第二种。判断方法理清楚后,后面的定时清理脚本才有意义:只有满足「available 低 + dirty 高或换页明显」时才介入,而不是拿一条 cron 无脑刷缓存。
3. 定时清理落地:水位阈值 + drop_caches 的脚本与 cron 配置
3.1 思路:不要无脑刷,先看水位再动手
见过不少人的第一版方案是每分钟执行一次echo 3 > /proc/sys/vm/drop_caches,这里先把结论说清楚:这是饮鸩止渴。把整个 page cache 清空后,业务热数据下次访问全部重新读盘,瞬间 IO 飙升,数据库类应用直接被打穿。正确的姿势是设定水位阈值:只有当可用内存低于某个百分比,并且缓存量确实够大时才执行回收,这样每次清理都有实际收益,又不至于把热缓存全部牺牲掉。
阈值给两个:可用内存占比低于 20% 触发,Cached 大于 4GB 才动手。这两个数不是拍脑袋,page cache 小于 4GB 时强行清理的收益很小,反而白白损失缓存命中率。下面的脚本就是按这两个水位设计的。
3.2 完整脚本:获取内存、比较阈值、带锁执行
#!/usr/bin/env bash # kylin_mem_clean.sh - 银河麒麟V10 内存水位定时清理脚本 # 用法:直接由 crontab 调用,脚本自带阈值判断和并发锁 set -u # 阈值:可用内存占比低于 20% 且 Cached 大于 4GB 才清理 TRIGGER_PERCENT=20 MIN_CACHE_MB=4096 # 日志路径,后面 cron 和排查都用它 LOG_FILE="/var/log/kylin_mem_clean.log" # 锁文件,防止脚本上一次没跑完下一次又进来 LOCK_FILE="/tmp/.kylin_mem_clean.lock" # 用 flock 做互斥,拿不到锁直接退出(说明上一个实例还在跑) exec 9>"${LOCK_FILE}" if ! flock -n 9; then echo "$(date '+%F %T') [skip] another instance is running" >> "${LOG_FILE}" exit 0 fi # 从 /proc/meminfo 取四个关键数值,单位都是 kB TOTAL_KB=$(awk '/^MemTotal:/ {print $2}' /proc/meminfo) AVAIL_KB=$(awk '/^MemAvailable:/ {print $2}' /proc/meminfo) CACHE_KB=$(awk '/^Cached:/ {print $2}' /proc/meminfo) DIRTY_KB=$(awk '/^Dirty:/ {print $2}' /proc/meminfo) # bash 只支持整数运算,单位换算成 MB TOTAL_MB=$((TOTAL_KB / 1024)) AVAIL_MB=$((AVAIL_KB / 1024)) CACHE_MB=$((CACHE_KB / 1024)) DIRTY_MB=$((DIRTY_KB / 1024)) # 计算可用内存占比 AVAIL_PERCENT=$((AVAIL_MB * 100 / TOTAL_MB)) echo "$(date '+%F %T') [check] avail=${AVAIL_MB}MB (${AVAIL_PERCENT}%), cached=${CACHE_MB}MB, dirty=${DIRTY_MB}MB" >> "${LOG_FILE}" # 两个水位条件都满足才真正动手 if [[ ${AVAIL_PERCENT} -lt ${TRIGGER_PERCENT} && ${CACHE_MB} -gt ${MIN_CACHE_MB} ]]; then # 先 sync 把脏页写回磁盘,避免清缓存时把回写压力全压到业务 IO 上 sync # 清理 page cache 和 dentry/inode:1=page cache, 2=inode/dentry, 3=两者 echo 3 > /proc/sys/vm/drop_caches # 清理后再取一次数据,落日志供后续核对效果 AVAIL_KB_AFTER=$(awk '/^MemAvailable:/ {print $2}' /proc/meminfo) AVAIL_MB_AFTER=$((AVAIL_KB_AFTER / 1024)) echo "$(date '+%F %T') [clean] avail after=${AVAIL_MB_AFTER}MB, dirty=0MB" >> "${LOG_FILE}" else echo "$(date '+%F %T') [skip] threshold not reached" >> "${LOG_FILE}" fi这段脚本有几个细节值得单独说明。第一是flock -n:cron 触发的脚本如果执行时间过长,前一个还没跑完下一个又启动,会出现两条进程同时在刷缓存,日志里全是重复记录。用锁文件把并发情况直接 skip 掉,保证同一时刻只有一个人在干活。第二是阈值判断用的[[ ... ]]语法,两个条件用&&连接,含义是“只在内存真紧张且缓存足够大时才清”,缺一个条件都跳过。第三是echo 3之前必须先sync:不清的情况下直接 drop_caches,大量脏页会被强制写回,磁盘瞬间被写满,后来我吃过这个亏。
如果想把触发调得更保守,只需要改两个变量:TRIGGER_PERCENT从 20 调到 15,表示可用内存低于 15% 才动手;MIN_CACHE_MB从 4096 调到 8192,表示缓存要堆积到 8GB 以上才回收。数据库或者 Redis 所在的机器建议都调保守一档。
3.3 挂到 crontab:周期、日志与重启后的检查
脚本写好后放到固定目录,用crontab -e加入定时任务:
# 每 5 分钟跑一次,标准输出和错误都进同一个日志文件 */5 * * * * /opt/scripts/kylin_mem_clean.sh >> /var/log/kylin_mem_clean.log 2>&1选每 5 分钟而不是每分钟,是因为清理动作本身会带来一次短暂的 IO 抖动,5 分钟的间隔给系统留了恢复时间,也足够覆盖“内存缓慢爬升”这类场景。如果你的业务内存波动特别快,比如夜间的批处理任务会在 1 小时内吃掉大部分内存,可以把周期改成*/2,但代价是缓存重建频率变高,需要后续观察 IO wait。
关于 cron 有件容易翻车的事:cron 环境变量非常少,脚本里所有命令都要用绝对路径,比如awk在某些精简系统里不在默认 PATH 里,建议脚本开头加上export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin。另外脚本前的注释不是装饰——排查时定位阈值不好用,翻日志是最直接的路径。
提示:服务器重启后 crontab 任务仍然在,不用担心。但
flock锁文件在 /tmp 下,重启后自动消失,不需要手工清理。
4. 三种回收姿势对比:无脑 echo 3、水位脚本与内核参数调优
4.1 定时 echo 3:简单但容易把业务缓存打穿
第一版方案长这样:
# 每 10 分钟清一次缓存(不推荐用于生产) */10 * * * * /bin/sync; echo 3 > /proc/sys/vm/drop_caches它能解决“内存看起来满”的症状,但代价是彻底的缓存命中率伤害。我见过一台部署了 Java 应用的麒麟V10,用这套跑了一周,内存确实降下来了,但业务接口延迟从 30ms 涨到平均 120ms——因为热点数据每次都要重新读磁盘。另外一个隐患是它没有条件判断,即使系统内存充足、缓存完全健康,也会被无差别清除。
结论是它只适合两种场景:临时测试验证清理机制本身是否生效,或者重启前的内存整理。生产环境把它放 cron 里长期跑,迟早出事故。
4.2 水位阈值脚本:折中方案,适合大多数 Web 服务器
第 3 章给的脚本就是这一档。它在“业务需要内存”和“缓存利用”之间取了个平衡点:平时不动缓存,只在可用内存跌破 20% 且缓存堆积超过 4GB 时才清一次,相当于给内核的缓存回收机制加了一个“定时巡检”。
这一档适合大多数 Web 服务器、Spring Boot 应用、容器化节点。这类负载的特点是内存使用率会缓慢爬升,但进程本身不存在泄漏,爬升的主因是文件缓存和 JVM 堆外内存的累积。水位脚本保证了清理动作只在真正有压力时发生,对业务影响降到了最低。
在我的实践里,Web 服务器跑这套方案三个月,最明显的变化是以前一两周就要手动重启业务、释放内存的操作彻底停掉了。日志里每天实际触发清理的次数大概 2~5 次,从未出现连续触发的情况。
4.3 调内核参数:vm.dirty_ratio 与 swappiness 的治本玩法
如果水位脚本还不够,或者机器上跑的是数据库、Redis 这类对 IO 极敏感的服务,可以考虑从内核参数层面让内存回收更积极。常见做法是调整/etc/sysctl.conf:
# 脏页达到可用内存 30% 时,进程写文件会被阻塞强制刷盘 vm.dirty_ratio = 30 # 脏页达到 5% 时,内核后台就开始写回,避免积压 vm.dirty_background_ratio = 5 # 内存压力较大时更积极回收缓存而非换出进程内存 vm.vfs_cache_pressure = 100 # 减少 swap 使用,避免换页抖动 vm.swappiness = 10调完执行sysctl -p生效。dirty_ratio 从默认的 20 提到 30,是给业务多点写缓冲;dirty_background_ratio 从默认 10 降到 5,是让后台回写提前介入,避免脏页突然堆积到阈值。这两个参数配合得当,很多“内存不释放”的问题在源头上就缓解了:脏页不会积到触发 drop_caches 的程度。
另外别忽略日志服务占的内存。journald 默认把日志存在/run/log/journal,而/run是 tmpfs,会真实占用内存。日志量大的机器内存被日志挤掉几百 MB 很常见,顺手做下限制:
# /etc/systemd/journald.conf SystemMaxUse=200M RuntimeMaxUse=100M三档方案的选择可以参考这个对比:
| 方案 | 适用场景 | 潜在风险 | 动手成本 |
|---|---|---|---|
| 定时 echo 3 | 临时排查、验证机制 | 缓存被打穿、IO 飙升 | 最低 |
| 水位阈值脚本 | Web、Java、容器节点 | 大促峰值仍需人工关注 | 低,一次部署长期有效 |
| 内核参数调优 | 数据库、Redis、IO 敏感服务 | 参数不当会让写压力前移 | 中,需观察调整 |
5. 定时刷内存避坑清单:五个高频翻车现场
5.1 现象:刷完瞬间磁盘打满、系统更卡
清理后系统直接卡顿几十秒,iostat看到 util 接近 100%,业务超时一片。原因是脚本里没有 sync,或者 sync 之后立即 drop_caches:大量脏页在 drop 时被强制写回,压力全部集中到业务 IO 路径上。解决方法是脚本里保留sync,并且把清理动作放到业务低峰时段。我后来习惯在脚本里再加一层判断:如果当前vmstat 1 1的 wa 已经超过 50%,这次直接 skip,宁可留着缓存也别雪上加霜。
5.2 现象:清了没效果,free 反而更高、更卡
有一种情况是清理后 used 变低了,但业务访问数据库的延迟反而升高。原因很直接:数据库的 InnoDB buffer pool 本身就在 page cache 里,清缓存相当于把数据库热数据全部从内存赶了出去,后续查询全部落盘。这属于典型的“误杀”。解决方法是数据库、Redis 所在机器不要用echo 3,改用echo 1只清 page cache 保留目录项,或者干脆只做内核参数调优,让缓存继续留在内存里服务业务。从那以后我给数据库服务器定的规矩是:禁用水位清理脚本,只调 dirty 参数。
5.3 现象:清理后内存很快又满了,脚本根本没被触发
如果日志里全是[skip] threshold not reached,但 available 实际已经很低,要警惕另一类情况:内存是被进程真实占用的,不是缓存。常见的是 JVM 堆内存设置过大、WPS 或搜狗输入法这类桌面组件常驻、还有 Java 进程的堆外内存累积。这属于真增长,drop_caches 对它们没有任何作用,需要另查进程。排查方法是用ps aux --sort=-%mem | head -20找 RSS 大户,再针对 JVM 用jmap -heap看堆内堆外占比。注意这里别把 JVM 内存模型和系统 page cache 混为一谈,前者是应用层的事,后者才是我们这套清理脚本的管辖范围。
5.4 现象:cron 任务没生效,日志里什么都没有
最常见的原因是脚本没有执行权限,或者脚本第一行#!/usr/bin/env bash缺失;第二个高发原因是脚本里用了相对路径,cron 的工作目录和登录 shell 不一样;第三个是我踩过的:脚本里有中文注释但文件编码不是 UTF-8,在set -u模式下脚本直接报错退出。排查顺序就三步:先crontab -l确认任务在,再bash -x /opt/scripts/kylin_mem_clean.sh手动跑一遍看输出,最后看/var/log/cron里有没有该任务的执行记录。手动能跑、定时不跑,基本都出在环境变量或路径上。
5.5 现象:swap 频繁换页,si/so 成了性能瓶颈
内存清理后系统仍然慢,vmstat的 si/so 持续非零。这个现象说明问题的根源不是缓存堆积,而是物理内存确实不够用,内核在不停地换页。此时刷缓存只能缓解一时,正确的做法是:降低 swappiness(比如调到 10)、检查是否有进程内存配置过大、给机器合理扩容。另外要确认 swap 是不是建在机械盘或者高 IO 的存储上——换页本身就在消耗磁盘带宽,如果 swap 和业务数据在同一个盘,等于互相拖累。
6. 验证清理是否生效:先用日志和 vmstat 盯三天再调参
方案上线后不能只看 free 一时变低,要验证“清理是否真的有效且无副作用”。我的习惯是在脚本里加一个 dry-run 观察模式:先用两周的日志数据做基准,再正式触发清理。具体做法是把脚本中真正执行echo 3的那段临时注释掉,只保留记录部分,然后连续三天用这条命令采样:
# 每 10 秒记录一次 available、dirty、si/so,落盘供分析 watch -n 10 "date '+%F %T'; grep -E 'MemAvailable|Dirty' /proc/meminfo; vmstat 1 2 | tail -1" >> /tmp/mem_trend_$(date +%Y%m%d).log观察指标就三个:available 是否在业务高峰被压低到 20% 以下、dirty 是否持续超过物理内存的 10%、si/so 是否频繁非零。三天数据里如果每天都有满足触发条件的时段,说明脚本有必要;如果三天里 available 从未跌破 30%,那问题的根源根本不在缓存回收,多半要回到进程层排查。
验证正式生效的标准是:清理触发后 5 分钟内 available 明显回升,dirty 归零,同时业务接口延迟没有出现陡增。如果清理完成但 available 半小时内又被拉回低水位,说明内存是被真增长占掉的,脚本需要停用并转向排查应用。还记得第一次给麒麟V10 部署这套方案时,我没有先观察就直接把 cron 挂上,结果白天业务高峰触发清理,数据库热缓存被打穿,叫了一整晚。从那以后我每次处理内存问题,不管用户催得多急,都强制先跑三天监控数据再上定时脚本,最后再调阈值——用数据说话,不拿生产环境试错。希望帮到你。
本文还有配套的精品资源,点击获取