简介面向银河麒麟V10服务器运维人员的轻量级脚本包聚焦系统长时间运行后内存不释放、可用内存逐步下降的问题适合需要快速建立周期化清理与日常维护机制的运维场景。压缩包为rar格式共3个文件由2个sh脚本和1个txt配置说明组成整体约2KB体积小巧便于在服务器环境中留存并按说明部署。已有793人学习。包内提供定时任务与内存释放脚本及配套配置说明读者可据此了解脚本的调度方式、参数设置与适用边界将重复的手动清理转为定时执行减少人工干预。它更适合作为内存泄漏根因排查前的临时缓解手段配合系统监控、日志分析和参数检查使用帮助降低内存持续占用引发的性能波动风险对初中级运维人员具有参考价值。1. 内存不释放不是“清缓存”四个字先分清页缓存、Slab 和匿名页银河麒麟 V10 上出现内存不释放运维圈里最常见的定时办法就是sync之后往/proc/sys/vm/drop_caches写 3。这个动作能缓解一部分“看着内存高”的告警但它只处理可回收的页缓存、dentry 和 inode不碰应用进程的匿名页、堆外内存和不可回收 Slab。把这套定时清理直接套到数据库、消息队列或长期运行的 Java 服务上很容易出现“缓存降了、RSS 没降、过半小时又涨回来”的翻车现场。这里要讲的是一套按MemAvailable阈值触发、带日志和回滚的定时释放办法适合负责银河麒麟 V10 服务器可靠性的运维也适合刚开始接手国产 Linux 环境、不想靠重启解决一切的人。先认清内存构成再决定定时任务清什么、多久清一次、清完看哪个指标后面的脚本和参数才有意义。2. 定时释放方案落地从阈值判定到 systemd timer 的完整脚本定时清理不是写一行 cron 就完事。我一般会把“到点无条件清”改成“到点先看阈值低了才清”这样空闲机器不会被无谓打扰出问题时日志也能说清是“没触发”还是“触发了但没权限”。落地顺序是定触发条件、写清理脚本、挂 systemd timer、补日志与退出码。2.1 先定触发条件MemAvailable 比 free 更可靠free的free列只代表完全没被用的内存Linux 会把大量空闲内存拿去做缓存所以这个数低并不等于马上要 OOM。真正该看的是/proc/meminfo里的MemAvailable它估算的是在不触发换出的前提下还能分配给新进程的内存。常见做法是按机器总内存留 10% 到 15%或者小内存机器直接设一个固定下限比如 1024 MB。先读一次当前值awk /^MemTotal:|^MemAvailable:|^Cached:|^SReclaimable:|^SUnreclaim:/{print $1, $2, $3} /proc/meminfo这条命令把总内存、可用内存、页缓存、可回收 Slab 和不可回收 Slab 一起打出来单位是 kB。参数不用改重点看MemAvailable和SUnreclaim的比例。如果MemAvailable还有好几 GB定时任务就不该动手如果SUnreclaim持续上涨清理页缓存也救不了得去查内核对象或驱动。2.2 清理脚本sync 与 drop_caches 的执行顺序脚本我放在/usr/local/sbin/mem-reclaim.sh核心是 flock 防并发、阈值判断、先sync再写drop_caches。直接写 3 之前必须sync否则脏页还没落盘清缓存只会把写压力往后推。#!/usr/bin/env bash set -euo pipefail THRESHOLD_MB${THRESHOLD_MB:-1024} LOG_FILE${LOG_FILE:-/var/log/mem-reclaim.log} LOCK_FILE${LOCK_FILE:-/run/mem-reclaim.lock} exec 9$LOCK_FILE flock -n 9 || { echo $(date %F %T) skip: another run is active $LOG_FILE exit 0 } mem_available_kb$(awk /^MemAvailable:/{print $2} /proc/meminfo) mem_available_mb$((mem_available_kb / 1024)) if (( mem_available_mb THRESHOLD_MB )); then echo $(date %F %T) skip: MemAvailable${mem_available_mb}MB ${THRESHOLD_MB}MB $LOG_FILE exit 0 fi echo $(date %F %T) start: MemAvailable${mem_available_mb}MB ${THRESHOLD_MB}MB $LOG_FILE sync if [[ -w /proc/sys/vm/drop_caches ]]; then echo 3 /proc/sys/vm/drop_caches echo $(date %F %T) done: drop_caches3 $LOG_FILE else echo $(date %F %T) error: /proc/sys/vm/drop_caches is not writable $LOG_FILE exit 2 fiTHRESHOLD_MB是触发下限按机器规格改LOG_FILE建议留在/var/log下并配轮转LOCK_FILE放/run重启自动清掉。flock -n 9保证上一轮没跑完时下一轮直接跳过避免定时任务叠在一起。echo 3同时清页缓存、dentry 和 inode写之前用-w判断可写失败退出码为 2后面做告警时能区分“跳过”和“权限错误”。注意drop_caches不会释放应用进程的匿名页也不会回收不可回收 Slab。脚本只解决可回收缓存导致的MemAvailable偏低不解决内存泄漏。2.3 systemd timer 与 cron 的取舍cron 写起来快但日志、依赖顺序和失败重试都弱。银河麒麟 V10 上既然有 systemd我一般用 service 加 timer查状态和补跑都方便。先写 service# /etc/systemd/system/mem-reclaim.service [Unit] DescriptionReclaim page cache when MemAvailable is low Afternetwork.target [Service] Typeoneshot EnvironmentTHRESHOLD_MB1024 ExecStart/usr/local/sbin/mem-reclaim.sh Nice10 IOSchedulingClassbest-effort IOSchedulingPriority7Typeoneshot适合跑完就退出的脚本Environment把阈值写进 unit避免依赖登录 shell 的环境变量Nice和IOSchedulingPriority降低清理动作对业务 IO 的抢占。接着写 timer# /etc/systemd/system/mem-reclaim.timer [Unit] DescriptionRun mem-reclaim every 15 minutes [Timer] OnBootSec5min OnUnitActiveSec15min AccuracySec1min RandomizedDelaySec30s Persistenttrue [Install] WantedBytimers.targetOnBootSec5min让开机后先跑一轮业务OnUnitActiveSec15min表示上一次激活后 15 分钟再跑AccuracySec1min允许内核合并唤醒减少无谓唤醒RandomizedDelaySec30s把多台机器的执行时间打散避免同一时刻集中清缓存Persistenttrue在错过后补跑一次。启用和检查sudo chmod 0755 /usr/local/sbin/mem-reclaim.sh sudo systemctl daemon-reload sudo systemctl enable --now mem-reclaim.timer systemctl list-timers --all | grep mem-reclaim如果只想看下一次触发时间list-timers足够想看历史执行用journalctl -u mem-reclaim.service。cron 也能用但需要自己处理锁、日志和失败告警机器一多就容易变成黑匣子。2.4 日志与退出码让定时任务可审计脚本日志至少要有时间、当前MemAvailable、阈值、动作和结果。排查时先看日志尾部再看 systemd 状态tail -n 20 /var/log/mem-reclaim.log systemctl status mem-reclaim.service --no-pager journalctl -u mem-reclaim.service -n 50 --no-pager日志建议加 logrotate不然一年后一个文件能拖慢tail/var/log/mem-reclaim.log { weekly rotate 8 compress missingok notifempty create 0640 root root }退出码约定也要统一0 表示跳过或成功2 表示/proc/sys/vm/drop_caches不可写其他非零留给脚本自身错误。这样后面接告警时不会把“正常跳过”误报成“清理失败”。3. 参数怎么设drop_caches、swappiness、vfs_cache_pressure 的边界这套办法里最容易拍脑袋的是参数。drop_caches写几、swappiness调不调、vfs_cache_pressure要不要动答案都取决于业务内存形态。运维参数不是越大越勤就越好清理太猛会把热点缓存打掉业务重新读盘延迟反而上去。3.1 drop_caches 的三种取值与可回收范围/proc/sys/vm/drop_caches接受 1、2、3 三个值分别对应页缓存、dentry/inode、两者都清。写之前先sync把脏页刷下去否则清完缓存脏页还在写压力并没消失。sync echo 1 /proc/sys/vm/drop_caches # 只清 pagecache echo 2 /proc/sys/vm/drop_caches # 清 dentry 和 inode echo 3 /proc/sys/vm/drop_caches # 两者都清定时脚本里我一般只用 3因为 dentry/inode 在大量小文件场景下涨得很快单独清页缓存经常看不出MemAvailable的变化。但 3 也不是万能它触发内核回收可回收对象不释放进程堆内存不回收SUnreclaim。如果清理后Cached降了、SUnreclaim没降说明问题不在页缓存。3.2 vm.swappiness 不是“内存回收开关”vm.swappiness控制匿名页换出倾向默认常见是 60。把它设成 0 并不等于“禁止用 swap”只是尽量不主动换出匿名页内存压力大时仍可能 OOM。数据库和延迟敏感服务通常会把值调低但调到 0 要谨慎。sysctl vm.swappiness sysctl -w vm.swappiness10临时改完立即生效重启失效。要持久化就写/etc/sysctl.d/99-mem-reclaim.confecho vm.swappiness 10 /etc/sysctl.d/99-mem-reclaim.conf sysctl --system参数建议通用服务 10 到 30数据库、缓存中间件先看厂商建议小内存机器不要为了“看起来不用 swap”强行设 0。swappiness调低减少换出不等于减少页缓存和定时drop_caches是两件事。3.3 vfs_cache_pressure 与 dentry/inode 缓存vm.vfs_cache_pressure默认 100值越大内核越倾向于回收 dentry 和 inode 缓存。大量小文件、频繁 stat 的场景这个值调太高会让元数据缓存反复失效业务反而更慢文件服务长期不释放 dentry可以适度上调但别一步干到 1000。sysctl vm.vfs_cache_pressure sysctl -w vm.vfs_cache_pressure150持久化同样写进/etc/sysctl.d/99-mem-reclaim.conf。判断依据看SReclaimable和SUnreclaim可回收 Slab 高调这个值有意义不可回收 Slab 高调了也白调得查内核模块或文件系统。参数常见值作用调整建议vm.drop_caches1/2/3触发回收页缓存、dentry/inode定时任务用 3先sync不要高频vm.swappiness60匿名页换出倾向通用 10 到 30数据库按业务验证vm.vfs_cache_pressure100dentry/inode 回收倾向小文件多可试 150不要盲目拉满THRESHOLD_MB1024 或总内存 10%触发清理的可用内存下限按机器规格和业务峰谷定3.4 阈值、周期、并发定时任务的三个参数阈值太低清理频繁阈值太高等于没触发。周期太密热点缓存刚建立就被打掉周期太疏告警来了还没跑。并发必须加锁否则多实例或手动执行会叠在一起。我的常用组合是THRESHOLD_MB取总内存 10% 到 15%周期 15 到 30 分钟RandomizedDelaySec30 秒到 2 分钟flock必开。要改 timer 周期不用直接编辑原文件用 drop-insudo systemctl edit mem-reclaim.timer在打开的片段里写[Timer] OnUnitActiveSec30min RandomizedDelaySec2minOnUnitActiveSec覆盖原周期RandomizedDelaySec打散多机执行。改完systemctl daemon-reload再systemctl restart mem-reclaim.timer。参数改完至少观察一天别刚改完就看一眼free下结论。4. 避坑与排查定时任务跑完内存没降的五个现场定时清理最容易出现的错觉是“脚本跑了内存就该降”。实际现场里跑完没变化、降了又涨、手动有效自动无效都很常见。下面五条按“现象 → 原因 → 解决”拆开都是一线运维踩过的坑。4.1 现象free 的 buff/cache 降了业务 RSS 没降执行完脚本free -m里buff/cache明显下降但业务进程的 RSS 几乎不变MemAvailable只回升一点。原因是drop_caches只回收页缓存和 dentry/inode不回收进程匿名页业务进程的堆内存、线程栈、直接内存都不在清理范围。先确认进程内存构成grep -E ^(RssAnon|RssFile|RssShmem): /proc/pid/statusRssAnon高说明匿名页多清理缓存解决不了RssFile高才可能和文件页缓存有关。解决方向是查应用泄漏、连接池、缓存配置必要时滚动重启而不是把定时清理周期越调越短。把drop_caches当内存泄漏的后悔药最后只会把问题拖到 OOM。4.2 现象脚本手动跑有效timer 跑无效手动bash /usr/local/sbin/mem-reclaim.sh能看到donetimer 日志里却只有skip或权限错误。常见原因是 service 环境里没有THRESHOLD_MB或者PATH不同导致awk、flock找不到也可能是 timer 以非 root 运行写不了/proc/sys/vm/drop_caches。先看 service 实际环境systemctl show mem-reclaim.service -p Environment -p ExecStart -p User journalctl -u mem-reclaim.service -n 80 --no-pager解决方式是把阈值写进 unit 的Environment脚本里用绝对路径确认 service 没有降权运行。手动执行时 shell 里的环境变量不会自动带进 systemd这一点最容易翻车。4.3 现象清理后 IO 抖动业务超时脚本一跑磁盘读 IO 上来接口延迟抖一下高峰期满屏超时。原因是热点页缓存被清掉业务重新读盘同时sync把脏页集中刷下去写压力短时抬高。解决不是简单加频率而是降低触发频率、避开业务高峰、给 service 加Nice和 IO 优先级并把阈值调低让清理只在真正低可用内存时发生。如果业务对延迟极敏感宁可不做定时drop_caches改用应用侧缓存上限和容量规划。4.4 现象内存短暂下降又快速涨回清理后MemAvailable回升几分钟又掉回去告警反复。原因是业务工作集本来就这么大页缓存被清掉后很快又被读回来或者存在持续泄漏清一次只是把曲线压出一个坑。先连续采样看趋势不要看单点vmstat 1 10 awk /^MemAvailable:|^Cached:|^SUnreclaim:/{print $1, $2, $3} /proc/meminfo如果MemAvailable长期缓慢下降、SUnreclaim持续上涨优先查泄漏和内核对象如果只是工作集稳定在高位清理周期再密也只是反复打缓存收益很低。4.5 现象日志里只有 sync没有 drop_caches日志出现start但没有done脚本退出码非零。常见原因是set -euo pipefail下echo 3 /proc/sys/vm/drop_caches写失败直接退出或者/proc/sys/vm/drop_caches在当前环境不可写。先看权限和挂载id ls -l /proc/sys/vm/drop_caches脚本里已经用-w判断可写并输出错误日志排查时重点看退出码 2。不要用chmod去改/proc下文件的权限那是内核接口改不动也不该改应该确认执行身份和系统策略。5. 验证与灰度用 meminfo、slabtop、vmstat 做前后对照定时清理上线前血泪经验是先做基线不要凭感觉定阈值。同一台机器连续采一段时间看MemAvailable的低谷出现在什么时候、Cached和Slab各占多少再决定阈值和周期。上线后也要灰度先单机跑再小批量最后全量。5.1 建立基线连续采样而不是看单点下面这段脚本每 10 秒采一次采 30 次输出 CSV方便丢进表格看趋势#!/usr/bin/env bash OUT${1:-/tmp/mem-baseline.csv} echo ts,MemTotal_kB,MemAvailable_kB,Cached_kB,SReclaimable_kB,SUnreclaim_kB $OUT for i in $(seq 1 30); do ts$(date %F %T) awk -v ts$ts /^MemTotal:/{t$2} /^MemAvailable:/{a$2} /^Cached:/{c$2} /^SReclaimable:/{sr$2} /^SUnreclaim:/{su$2} END{printf %s,%s,%s,%s,%s,%s\n, ts,t,a,c,sr,su} /proc/meminfo $OUT sleep 10 done$1是输出路径默认/tmp/mem-baseline.csvseq 1 30和sleep 10控制采样时长约 5 分钟。要在业务高峰和低峰各跑一次别只挑空闲时段。CSV 里SUnreclaim_kB如果持续走高先别急着上定时清理查内核对象更实际。5.2 清理前后对照MemAvailable、Cached、Slab清理前记录一次手动执行脚本后再记录一次重点看三个指标MemAvailable是否回升、Cached是否下降、SUnreclaim是否不动。辅助命令free -m slabtop -o -s c | head -n 20 vmstat 1 5slabtop -o -s c按缓存大小排序看哪些内核对象占大头vmstat 1 5看si/so和bi/bo判断有没有换出和读 IO 抖动。对照表可以这样记指标清理前清理后判断MemAvailable低明显回升可回收缓存占大头Cached高下降页缓存被回收SReclaimable高下降dentry/inode 被回收SUnreclaim高基本不变清理无效查内核对象RssAnon高基本不变应用匿名页查泄漏或容量如果MemAvailable回升但业务延迟变差说明热点缓存被打掉周期或阈值要回调。验证不是看脚本有没有done是看清理前后业务指标和内存指标是否对得上。5.3 灰度与回滚先单机后批量上线顺序我一般分三步先在一台低峰机器enable --now观察 24 小时再选 5 台不同角色机器观察一个业务周期最后全量。每步都保留回滚命令sudo systemctl disable --now mem-reclaim.timer回滚后确认 timer 不再触发systemctl list-timers --all | grep mem-reclaim || echo timer disabled灰度期间重点看日志里的skip比例。如果大部分时间都在skip说明阈值偏低或周期太密如果每次都是start且清理后很快又低说明问题不是缓存定时清理只是在拖时间。5.4 告警规则别让清理任务自己静默失败定时任务最怕静默失败。可以给 service 配OnFailure也可以每天检查日志里有没有成功记录。简单做法是用一个检查脚本看最近一次done的时间超过预期周期两倍就告警。systemd 侧可以加[Unit] OnFailuremem-reclaim-alert.servicemem-reclaim-alert.service里放发邮件、写事件平台或调内部告警的命令。日志侧则定期 grepgrep done: drop_caches3 /var/log/mem-reclaim.log | tail -n 1告警规则不要只看“服务是否 active”oneshot 服务跑完就是 inactive容易误判。看 timer 下一次触发时间、看日志最后成功时间、看退出码三样合起来才靠谱。6. 进阶技巧把“定时清理”改成“阈值触发可回滚”的运维习惯定时清理写到最后拼的不是脚本多长而是三件事有没有做到触发有阈值、执行有上限、失败有回滚。我一般会把THRESHOLD_MB、周期、日志路径做成 unit 环境变量换机器只改 unit 不改脚本timer 加RandomizedDelaySec避免多台机器同一秒清缓存service 加Nice和 IO 优先级别让清理动作和业务抢磁盘。动作建议原因触发条件看MemAvailable不看free单列避免把缓存当泄漏执行频率15 到 30 分钟加随机延迟减少集中抖动并发控制flock -n防止上一轮没跑完又叠一轮回滚disable --nowtimer出问题先停定时再查业务验证单机 24 小时再 5 台再全量避免一次推全量翻车如果只记一个技巧我建议把 timer 的OnUnitActiveSec和RandomizedDelaySec写进 drop-in而不是直接改原 unitsudo systemctl edit mem-reclaim.timer[Timer] OnUnitActiveSec30min RandomizedDelaySec2min这样升级或替换脚本时原 unit 不动周期参数还在。改完systemctl daemon-reload systemctl restart mem-reclaim.timer再看list-timers确认下一次触发时间。从那以后我每次上这类定时清理都强制先跑一遍基线采样和回滚演练再决定阈值和周期。希望帮到你。本文还有配套的精品资源点击获取