linux程序内存占用持续增长killed
最近在CentOS上运行自己写的程序,程序运行时间久一点就被killed,需要分析原因并找到解决方法.
首先可能原因是:
- 内存不够
- 程序出错
内存不够问题
查看linux 系统日志.
1 |
|
如果出现 kernel: Out of memory: Kill process
意味着整个系统的内存已经不足,如果不杀死进程的话,就会导致系统的崩溃.
用free命令查看虚拟内存
1 |
|
查看某个进程的内存使用情况,使用top命令
1 |
|
出现如下情况
监控系统报警生产服务进程出现,内存使用率达到 90% 以上
python程序在长时间(较大负载)运行一段时间后, python 进程的系统占用内存持续升高:
1 |
|
这里的python进程在经历大量请求处理过程中, 内存持续升高, 直至最终被killed.
查证过程
昨天有7个record没有返回数据的情况,查日志均是 ==ASR/Slot timeout== 的原因导致。猜测是线程之间发生死锁,gc不能回收,使得多个线程全部挂起,导致内存一直在增长 以及 record服务超时!
具体查证过程如下:
top 查看 CPU 和内存占用
1 |
|
strace 查看系统调用
1 |
|
ltrace 查看库函数调用
1 |
|
gcore 生成 coredump 文件
为了避免 gdb attach
进程造成的其他影响(比如可能出现进程异常退出,死锁突然恢复,影响线上服务等),最好将进程生成一个 coredump 文件,然后再慢慢分析。
1 |
|
gdb 分析 coredump 文件
定位异常:确定python在做什么,是否有大内存消耗任务正在运行,或出现死锁等异常行为
接入gdb
1 |
|
使用 info threads
查看当前进程的线程列表,发现大部分都在 wait
信号,只有25号线程在做其他事情,切换到25号线程,分析调用栈:
1 |
|
查看线程栈信息,
1 |
|
info stack,这个命令只能查看当前正在运行的某个线程的栈信息
使用原始 bt
来分析(添加 full
参数可以看更详细的内容):
1 |
|
分析 Frame # 7
发现当前线程正在执行 ./service/recall/newuser.py, line 49, in get_gametype_anchor_by_sn
方法。
找到对应的源代码。通过仔细分析代码,发现在某种情况下确实会出现死循环情况,至此问题解决。
死锁解决办法:
- 尝试用锁加以隔离
- 取消多线程
- 手动释放内存:对大的变量存储对象,在使用完成后先del掉,然后在return之前,统一gc.collect()垃圾回收一下
总结
- 遇到线上问题时,优先使用
gcore PID
来保存现场 - 再使用strace、ltrace和
gdb
分析 - 如果没有什么线索,可以尝试 pyrasite-shell 或 lptrace
gdb
调试Python
进程的时候,运行进程的Python
版本和 python-dbg 一定要匹配
参考
附录:gdb&tracemalloc
python本身是有垃圾回收的, 但python有如下几种情况可能出现内存泄露(即导致对象无法被回收):
- 循环引用
- 循环引用的链上某个对象定义了
__del__
方法. - 对象一直被全局变量所引用, 全局变量生命周期长.
- 垃圾回收机被禁用或者设置成debug状态, 垃圾回收的内存不会被释放.
- 引用对象未释放(数据库连接等)
gdb安装教程
详细信息可以参考 debug-with-gdb
1 |
|
添加以下内容至文件中:
1 |
|
安装依赖
1 |
|
完成!
查看python内存泄露的工具
tracemalloc —- 跟踪内存分配: 究极强, 可以直接看到哪个(哪些)对象占用了最大的空间(内存块), 这些对象是谁, 调用栈是啥样的, python3直接内置, python2如果安装的话需要编译. 它提供以下信息:
- Traceback where an object was allocated
- 每个文件名和每行号分配的内存块的统计信息:分配的内存块的总大小,数量和平均大小
计算两个快照之间的差异以检测内存泄漏
显示前10项
1
2
3
4
5
6
7
8
9
10
11
12import tracemalloc
tracemalloc.start()
# ... run your application ...
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')
print("[ Top 10 ]")
for stat in top_stats[:10]:
print(stat)- 计算差异
1
2
3
4
5
6
7
8
9
10
11
12
13import tracemalloc
tracemalloc.start()
# ... start your application ...
snapshot1 = tracemalloc.take_snapshot()
# ... call the function leaking memory ...
snapshot2 = tracemalloc.take_snapshot()
top_stats = snapshot2.compare_to(snapshot1, 'lineno')
print("[ Top 10 differences ]")
for stat in top_stats[:10]:
print(stat)- 显示最大内存块的回溯的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15import tracemalloc
# Store 25 frames
tracemalloc.start(25)
# ... run your application ...
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('traceback')
# pick the biggest memory block
stat = top_stats[0]
print("%s memory blocks: %.1f KiB" % (stat.count, stat.size / 1024))
for line in stat.traceback.format():
print(line)pyrasite: 牛逼的第三方库, 可以渗透进入正在运行的python进程动态修改里边的数据和代码(其实修改代码就是通过修改数据实现)