Perl内存使用分析和泄漏检测?

35

我用Perl在Linux上编写了一个持久的网络服务。不幸的是,随着运行时间的增加,它的Resident Stack Size(RSS)会缓慢但肯定地增长,尽管我已经努力清除了所有不需要的哈希键,并删除了对对象的所有引用,否则将使引用计数保持不变并阻碍垃圾收集。

是否有任何好的工具可以分析Perl程序中各种本地数据基元、散列引用对象等所使用的内存?您用什么来跟踪内存泄漏?

我通常不会花时间在Perl调试器或任何交互式分析器上,因此希望得到一个温暖、温和的非晦涩回答 :-)


你搞明白了吗?根据你提供的信息,我的最佳猜测是有一个库(通过某个模块的动态加载器引入)是罪魁祸首... - Ether
1
这似乎已成为规范的“查找内存泄漏”问题,因为我的其他类似问题的答案都已合并到这里 :) 我实际上并没有三次回答一个问题; 随着时间的推移,多个线程已经合并在一起了。 - Ether
口误了...你的意思是“常驻集大小”...这个数字与堆栈无关。 - Domingo Ignacio
5个回答

15

你的对象之一可能存在循环引用。当垃圾回收器来处理回收该对象时,循环引用意味着该引用所引用的一切都永远不会被释放。您可以使用Devel::CycleTest::Memory::Cycle来检查循环引用。另外一个尝试的方法(虽然在生产代码中可能会变得昂贵,因此建议在未设置调试标志时禁用它)是在所有对象的析构函数中检查循环引用:

# make this be the parent class for all objects you want to check;
# or alternatively, stuff this into the UNIVERSAL class's destructor
package My::Parent;
use strict;
use warnings;
use Devel::Cycle;   # exports find_cycle() by default

sub DESTROY
{
    my $this = shift;

    # callback will be called for every cycle found
    find_cycle($this, sub {
            my $path = shift;
            foreach (@$path)
            {
                my ($type,$index,$ref,$value) = @$_;
                print STDERR "Circular reference found while destroying object of type " .
                    ref($this) . "! reftype: $type\n";
                # print other diagnostics if needed; see docs for find_cycle()
            }
        });

    # perhaps add code to weaken any circular references found,
    # so that destructor can Do The Right Thing
}

1
你可以使用Scalar::Util::weaken()函数来弱化一个现有的引用(以允许析构函数通过循环进行释放)-- http://search.cpan.org/~gbarr/Scalar-List-Utils-1.21/lib/Scalar/Util.pm - Ether
2
嗨Ether - 尝试了UNIVERSAL::DESTROY(),长时间运行服务并测试,但没有收到任何东西。同时,RSS持续上升。如果不是循环引用的问题,还可能是什么呢? - Alex Balashov
2
我真的不确定在析构函数中查找内存泄漏会对你有什么好处。如果你正在泄漏,那么DESTROY永远不会被调用。 - Scott S. McCoy
@Ether:非常感谢您的帮助。我使用了您的代码,它检测到了我的代码中存在一些循环引用问题。现在的问题是,由于代码有数千行,我无法确定循环引用具体出现在哪里。Devel::Cycle能否打印出确切的行数?[输出:Circular reference found while destroying object of type ActiveState::Config::Node! reftype: ARRAY] - Chankey Pathak

10
你可以使用Devel::Leak来搜索内存泄漏。然而,文档非常稀少...例如,一个人到底从哪里获取$handle引用以传递给Devel::Leak::NoteSV()?如果我找到答案,我会编辑这个回复。
好的,事实证明使用这个模块非常简单(代码无耻地从Apache::Leak中窃取):
use Devel::Leak;

my $handle; # apparently this doesn't need to be anything at all
my $leaveCount = 0;
my $enterCount = Devel::Leak::NoteSV($handle);
print STDERR "ENTER: $enterCount SVs\n";

#  ... code that may leak

$leaveCount = Devel::Leak::CheckSV($handle);
print STDERR "\nLEAVE: $leaveCount SVs\n";

我会尽可能地将代码放在中间部分,让 leaveCount 检查尽可能靠近执行结束(如果有的话) -- 在大多数变量已被解除分配的情况下(如果您无法使变量超出范围,可以将 undef 分配给它以释放其指向的任何内容)。


变量在示例中使用不一致,例如 $leave 和 $leaveCount。 - Lot105

4

下一步该尝试什么(不确定是否最好在Alex上面的问题后面发表评论):除了Devel::Leak之外,我会尝试以下方法:

尝试消除程序中“不必要”的部分,或将其分成单独的可执行文件(它们可以使用信号进行通信,或者通过命令行参数相互调用)--目标是将可执行文件简化为仍然展示错误行为的最小代码量。如果您确定不是您的代码在做这件事,请减少使用的外部模块数量,特别是那些具有XS实现的模块。如果可能是您自己的代码,请寻找任何可能存在问题的地方:

  • 绝对不能使用Inline::C或XS代码
  • 直接使用引用,例如\@list\%hash,而不是预分配的引用,如[ qw(foo bar) ](前者创建另一个引用,可能会丢失;在后者中,只有一个引用需要担心,通常存储在本地词法标量中
  • 间接操作变量,例如$$foo其中修改了$foo,这可能会导致变量的自动初始化(虽然您需要禁用strict 'refs'检查)

3

我最近使用NYTProf作为一个大型Perl应用程序的分析工具。它不跟踪内存使用情况,但它可以追踪所有执行的代码路径,这有助于找出泄漏源。如果你泄漏的是稀缺资源,如数据库连接,追踪它们的分配和关闭位置将有助于找出泄漏。


2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接