只要是跑起来的服务器程序,都有可能遇到内存泄露问题,或大或小。 可以用简单的看门狗方法,内存增加到一定程度就重启; 但是重启只是隐藏问题,遇到严重的内存泄露,只能正视问题,想办法找到内存泄露的源点。 这里我整理了一下ruby语言的内存泄露查找方法,欢迎反馈。
基本思路是这样:等待内存泄露到一定程度,进程的内存里面会大量充斥着没有被释放的对象, 随机获取内存中的数据,就可以知道是什么对象泄露了,从而定位问题。
我们先开始一个实验。创建文件leak.rb:
1 2 3 4 5 |
|
这个文件生成了太多没有释放的字符串,并且一直处于循环等待状态。
实际的应用程序代码比较多,不是那么明显就能发现内存泄露的代码,需要通过调试寻找线索。
首先我们要编译一个带有debug信息的ruby版本。参数加上-O0 -g
,O0
是为了防止优化掉一些调试的符号表信息。
如果你用的是rvm,可以采用下面的脚本:
1 2 3 4 5 6 7 8 9 10 |
|
然后我们在这个环境中执行程序,实际程序不要忘记安装支持的gem:
1
|
|
之后,我们需要调试这个程序。如果你在linux下面,请使用gdb
,
如果在OSX下面,请使用编译ruby工具链中的debug工具,
在我的机器OSX上面是用clang来编译的,所以我采用的是lldb
,
下面的例子以我的机器为准,gdb的命令其实也是一样的。
另外开一个终端,启动lldb
,然后连接上跑起来的进程:
1
|
|
上面改成你用ps aux|grep ruby
找到的进程号。
attach做的事情就是在你调试进程里面开一个线程,这样就能够获得所有的内存信息, 同时也不影响程序正常运行(只要你保证线程安全)。
然后我们要知道进程内存消耗状况。在调试环境里面,我们可以执行C语言的函数,
其中rb_eval_string
可以用来直接执行ruby代码。
我们首先需要做的是用ObjectSpace
来遍历和列出所有ruby对象:
1 2 3 4 |
|
列出来之前先要垃圾处理一下。因为ruby有解释器全局锁,执行上面的代码应该不会造成线程安全问题。
回到执行ruby leak.rb
的终端,可以看到打印出来的结果。
如果是实际运行的程序,你可能需要开启一个文件,把结果打印进去,而不是打印到标准输出里面:
1
|
|
结果如下:
1 2 3 4 5 6 |
|
发现String对象出奇地多,应该是内存泄露的主要组成部分。我们采样一下数据,看看是什么样的字符串:
1 2 3 |
|
结果:
1
|
|
根据这个信息,我们回到源代码里面,找到对应的部分,思考为什么没有释放这个字符串,从而解决内存泄露的问题。
我们甚至可以利用rb_eval_string
来动态修改代码和解决bug,不过在这个例子里面没有办法删除掉造成内存泄露的s
对象。如果你发现有方法,还请告诉我。
但是如果内存泄露发生在C语言部分,应该如何发现?这个留到下次再介绍。 还有就是如何调试生产环境的进程,这个也请等我研究清楚之后再分享给大家。
引用资料: