
我在管理AWS EC2 t2.small实例的项目时,遇到了一个相当棘手的问题:内存使用率不断上升,最终飙升到 90% 以上,导致 Web 应用性能下降。为了缓解问题,我们暂时设置了定时任务,每小时重启一次实例。虽然这是权宜之计,但远非根本解决方案。本文记录了我一步一步排查内存泄漏和优化内存使用的过程,希望对遇到类似问题的运维或开发同仁有所帮助。
一、环境说明与初步观察
EC2 实例信息:
- 实例类型:t2.small
- 内存:2GB
- 系统:Amazon Linux 2(基于 CentOS)
- 应用架构:Nginx + Node.js Web 应用 + MongoDB(远程)
问题描述:
- 内存使用率从启动时的 30% 缓慢上升至 90%+
- 负载并不高,请求量稳定
- 没有明显错误日志或崩溃,但性能逐渐下降
- 每小时重启实例可以临时缓解内存问题
二、排查思路总览
内存持续上升且无明显释放迹象,常见原因包括:
- Web 应用存在内存泄漏;
- 某些后台服务未正确释放资源;
- 使用了缓存机制但未设上限;
- 存在僵尸进程或守护进程泄漏;
- 操作系统层面调度问题。
三、排查步骤与实战操作
1. 使用 top 或 htop 实时查看内存占用情况
top -o %MEM
重点关注哪些进程常驻内存、内存占用是否持续增长。我观察到 node 进程在长时间运行后,RSS(常驻内存)持续增长。
2. 启用 ps 和 smem 精细分析进程内存
sudo yum install smem -y
smem -r -k | sort -nr -k 4 | head -10
这个命令可以帮助我看到各进程的 Proportional Set Size (PSS),更真实地反映共享内存带来的影响。
3. 检查 Node.js 应用的内存使用(可能的内存泄漏)
a. 查看 Node.js 内存使用限制:
node --v8-options | grep memory
默认 Node.js 只分配约 512MB 堆内存。我怀疑应用未释放对象或缓存导致堆内存增长。
b. 引入内存快照工具
安装 heapdump:
npm install heapdump --save
在应用中添加:
if (process.env.NODE_ENV === 'production') {
const heapdump = require('heapdump');
setInterval(() => {
const filename = `/tmp/heapdump-${Date.now()}.heapsnapshot`;
heapdump.writeSnapshot(filename, (err, filename) => {
if (!err) console.log('Heap dump written to', filename);
});
}, 1800000); // 每半小时 dump 一次
}
然后下载 .heapsnapshot 文件,使用 Chrome DevTools → Memory 工具分析对象增长情况。最终我发现某个缓存服务(自定义对象池)未设置清理逻辑,导致大量对象长期驻留内存。
4. 检查是否存在日志写入泄漏或文件句柄泄漏
lsof -p <PID> | wc -l
某次排查中我发现 node 子进程保持了大量未关闭的文件描述符,是日志模块未正确关闭流的问题。
5. 检查系统级缓存与 swap
a. 查看缓存和 swap:
free -m
输出示例:
total used free shared buff/cache available
Mem: 2048 1800 80 20 168 120
Swap: 512 300 212
如果 buff/cache 持续上升,可能是系统层面的缓存压力,可手动清理测试:
sudo sync; sudo sysctl -w vm.drop_caches=3
注意:仅用于临时测试,不建议频繁使用。
6. 启用 CloudWatch Memory Metrics(自定义)
默认 EC2 不显示内存使用图表,我通过如下方式开启:
a. 安装 CloudWatch Agent:
sudo yum install amazon-cloudwatch-agent
b. 配置内存监控:
编辑配置文件 /opt/aws/amazon-cloudwatch-agent/bin/config.json 添加:
{
"metrics": {
"metrics_collected": {
"mem": {
"measurement": [
"mem_used_percent"
],
"metrics_collection_interval": 60
}
}
}
}
然后启用并查看 CloudWatch 图表以定位内存增长周期。
四、最终解决方案
经过上述多维度排查,我确认问题为:
- Node.js 应用某个缓存组件在请求后未正确释放对象;
- 默认 V8 垃圾回收机制在低内存实例上表现不佳;
- 系统未启用内存使用限制与预警机制。
我做了如下处理:
- 优化代码,清理不再使用的引用;
- 设置缓存对象 TTL;
- 将 Node.js 启动参数加入堆限制:
node --max-old-space-size=384 app.js
使用 PM2 管理进程,并配置内存超限自动重启:
pm2 start app.js --max-memory-restart 400M
升级实例类型为 t3.medium(2 vCPU / 4GB 内存),避免资源瓶颈。
内存占用问题往往不是一次重启能解决的,它反映的是系统资源与应用设计之间的矛盾。通过本次排查,我深刻体会到工具监控 + 代码优化 + 系统调度三者结合的重要性。如果你也遇到类似问题,不妨试试上述方法,或进一步使用如 valgrind、strace 等工具深入排查底层问题。











