你的服务器是不是又“喘”了?
“老王,网站又打不开了,内存报警98%!”
“赶紧重启一下服务!”
“重启完好了,但俩小时后又满了…”
这样的场景你是不是也经历过?内存占用高,就像家里的下水道堵了,明明刚清过,没多久又满。今天咱们就聊聊怎么系统性地搞定内存占用高的问题,从“看见它高”到“知道为啥高”,再到“让它不高”,一步到位。

先来认识一下内存占用高的几种“死法”
内存占用高不一定是坏事,有时候是程序在积极利用缓存(比如Redis、MySQL的InnoDB buffer pool),这叫“好高”。真正要警惕的是:
- 内存泄漏:程序用完内存不释放,像拧不紧的水龙头,一点点把内存耗光。
- 突发高峰:比如秒杀活动,瞬间创建大量对象,把内存撑爆。
- 配置不当:比如Java堆内存设太大,或PHP进程数太多,把内存吃光。
- 系统缓存无限增长:某些场景下Page Cache不释放,导致可用内存越来越少。
搞清楚是哪种情况,才能对症下药。
诊断篇:拿什么武器看清内存真相
1. free -h:一眼看出内存总量和剩余
$ free -h
total used free shared buff/cache available
Mem: 15G 8.2G 1.2G 123M 5.8G 6.5G
Swap: 2.0G 0.0K 2.0G
关键看 available 列,这才是真正可用的内存(包括可回收的缓存)。如果available很低,说明真不够了。
2. top / htop:看哪个进程最吃内存
top -o %MEM <em># 按内存使用率排序</em>
找到PID后,可以用 pmap -x PID 看详细内存映射,或者用 smem 看更准确的内存统计(包括共享内存)。
3. 内存泄漏侦查:smem 和 /proc/PID/smaps
<em># 安装 smem(如果没有)</em>
apt install smem <em># Ubuntu/Debian</em>
yum install smem <em># CentOS</em>
<em># 查看进程实际物理内存(USS)</em>
smem -p -s rss -r | head -20
/proc/PID/smaps 里能看到每一块内存的详细属性,比如哪些是可回收的,哪些是匿名内存。
4. 系统层面的内存分布:slabtop
如果内存被内核slab消耗,用 slabtop 查看:
slabtop -o -s c <em># 按缓存大小排序</em>
能帮你发现是不是内核模块或驱动有内存泄漏。
实战篇:不同场景的内存优化秘籍
场景一:Java应用内存飙升(OOM频繁)
症状:Java进程占用很高,GC日志频繁 Full GC,最后 OOM。
优化思路:
- 堆内存设置是否合理(-Xmx 和 -Xms)
- 是否存在内存泄漏(对象一直引用不释放)
- 是否使用了大对象(比如大List、HashMap)
代码示例:用MAT分析堆转储
<em># 生成堆转储文件</em>
jmap -dump:live,format=b,file=heap.hprof <PID>
<em># 用Eclipse MAT打开分析,重点关注:</em>
<em># - Leak Suspects Report(内存泄漏嫌疑)</em>
<em># - Dominator Tree(谁占了最大内存)</em>
JVM参数调优示例(针对Tomcat):
export JAVA_OPTS="-Xms4g -Xmx4g -XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/opt/logs/heap.hprof"
场景二:PHP-FPM进程把内存吃光
症状:PHP-FPM 进程数很多,每个进程占用几百M,总内存爆了。
原因:PHP-FPM的 pm.max_children 太大,或者每个请求处理的数据量太大,内存没及时释放。
优化方案:
<em>; /etc/php-fpm.d/www.conf</em>
pm = dynamic
pm.max_children = 50 # 最大子进程数,根据内存计算:总内存 / 单进程最大内存
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 10
<em>; 单个进程最大请求数,防止内存碎片</em>
pm.max_requests = 500
同时,在代码中注意释放大变量:
<?php
<em>// 处理完大数据后,手动释放</em>
$bigArray = getBigData();
<em>// ... 使用 $bigArray</em>
unset($bigArray); <em>// 显式释放,让PHP尽快回收内存</em>
场景三:Python应用内存泄漏(比如Flask、Django)
症状:Python进程内存缓慢增长,重启后正常,过一段时间又高。
排查工具:tracemalloc、objgraph、memory_profiler。
代码示例:用tracemalloc定位泄漏点
import tracemalloc
import linecache
import os
def display_top(snapshot, key_type='lineno', limit=10):
snapshot = snapshot.filter_traces((
tracemalloc.Filter(False, "<frozen importlib._bootstrap>"),
tracemalloc.Filter(False, "<unknown>"),
))
top_stats = snapshot.statistics(key_type)
print("Top %s lines" % limit)
for index, stat in enumerate(top_stats[:limit], 1):
frame = stat.traceback[0]
print("#%s: %s:%s: %.1f KiB"
% (index, frame.filename, frame.lineno, stat.size / 1024))
line = linecache.getline(frame.filename, frame.lineno).strip()
if line:
print(' %s' % line)
other = top_stats[limit:]
if other:
size = sum(stat.size for stat in other)
print("%s other: %.1f KiB" % (len(other), size / 1024))
total = sum(stat.size for stat in top_stats)
print("Total allocated size: %.1f KiB" % (total / 1024))
<em># 在应用入口启用</em>
tracemalloc.start()
<em># ... 运行一段时间后</em>
snapshot = tracemalloc.take_snapshot()
display_top(snapshot)
常见泄漏场景:
- 全局变量或类属性存储了不断增加的数据(比如往list里无限append)
- 循环引用导致垃圾回收不及时(可使用
gc.collect()强制回收)
场景四:数据库缓存“吞”了太多内存(MySQL、Redis)
症状:MySQL占用内存很高,free -h 显示buff/cache很大,但可用内存很少。
MySQL优化:调整InnoDB缓冲池大小
<em># /etc/mysql/my.cnf</em>
[mysqld]
innodb_buffer_pool_size = 4G # 设为物理内存的70%左右,留点给OS
innodb_log_file_size = 1G
innodb_flush_method = O_DIRECT # 减少双缓冲,降低内存压力
Redis优化:设置最大内存和淘汰策略
<em># redis.conf</em>
maxmemory 4gb
maxmemory-policy allkeys-lru <em># 当内存满时,淘汰最近最少使用的key</em>
同时监控Redis内存碎片:INFO memory,如果碎片率高,考虑重启或调整 activedefrag。
场景五:Linux Page Cache 不释放
现象:free 看到buff/cache很大,但 available 却很小,应用申请内存时卡顿。
原因:内核为了性能,把大量文件缓存留在内存里,但应用需要内存时,内核会回收,只是回收可能触发IO慢。
解决方法:手动释放缓存(临时)
<em># 释放页缓存、目录项、inode</em>
sync; echo 1 > /proc/sys/vm/drop_caches <em># 只释放pagecache</em>
sync; echo 2 > /proc/sys/vm/drop_caches <em># 释放dentries和inodes</em>
sync; echo 3 > /proc/sys/vm/drop_caches <em># 释放全部</em>
更优雅的做法:调整 /proc/sys/vm/vfs_cache_pressure,默认100,可以调高到200让内核更积极回收缓存。
echo 200 > /proc/sys/vm/vfs_cache_pressure
预防篇:别再等内存报警了
1. 设置合理的内存报警阈值
比如可用内存低于20%就报警,而不是等到OOM。用Prometheus + node_exporter监控内存使用率。
2. 在代码里埋点
对于关键业务,记录每次请求的内存增量,比如PHP可以在入口和出口打印memory_get_usage()。
3. 定期重启“不干净”的服务
对于有内存泄漏但暂时无法修复的应用,可以用crontab每天凌晨重启一次,比如:
0 4 * * * systemctl restart php-fpm
4. 使用容器限制内存
如果用Docker,可以给每个容器设置内存上限,避免一个容器把整个物理机拖垮。
docker run -m 512m --memory-swap 512m myapp
总结:内存优化三板斧
第一板斧:看清本质
用free、top、smem、slabtop这些工具,分清内存是进程占了、内核占了还是缓存占了。available才是真的可用的。
第二板斧:对症下药
- Java调堆、查泄漏;
- PHP限制进程数、及时释放变量;
- Python用tracemalloc定位泄漏点;
- 数据库缓冲池别超过物理内存70%;
- Page Cache高了就调整vfs_cache_pressure。
第三板斧:日常防守
设置报警、定期重启、容器化限制,把问题扼杀在摇篮里。
记住,内存优化不是一次性工作,而是一个持续观察、持续调整的过程。希望你的服务器从此告别“内存告急”,稳稳当当跑业务!
最后,动手试试吧:
先跑一遍 free -h 看看你服务器的available还剩多少,再 top -o %MEM 看看哪个进程最贪吃。然后挑一个场景,按我们上面说的去优化一下。搞定了别忘了回来点个赞!















暂无评论内容