常见的Perl内存/参考泄漏模式?

时间:2010-02-08 18:01:12

标签: perl memory-leaks

我在Perl代码库中追逐几个潜在的内存泄漏,我想知道Perl中有关内存(错误)管理的常见缺陷。

您在Perl代码中观察到的常见泄漏模式是什么?

4 个答案:

答案 0 :(得分:19)

到目前为止,循环引用是最常见的泄漏的规范原因。

sub leak {
    my ($foo, $bar);
    $foo = \$bar;
    $bar = \$foo;
}

Perl使用引用计数垃圾收集。这意味着perl会保留在给定时间存在指向任何变量的指针的计数。如果变量超出范围且计数为0,则清除变量。

在上面的示例代码中,永远不会收集$foo$bar,并且在每次调用leak()后都会保留副本,因为这两个变量的引用计数均为1.

防止此问题的最简单方法是使用弱引用。弱引用是您访问数据时所遵循的引用,但不计入垃圾回收。

use Scalar::Util qw(weaken);

sub dont_leak {
    my ($foo, $bar);
    $foo = \$bar;
    $bar = \$foo;
    weaken $bar;
}

dont_leak()中,$foo的引用计数为0,$bar的引用计数为1.当我们离开子例程的范围时,返回$foo到池中,它对$bar的引用被清除。这会将$bar上的引用计数降为0,这意味着$bar也可以返回池中。

<强>更新 大脑问我是否有任何数据来支持循环引用很常见的断言。不,我没有任何统计数据显示循环引用很常见。它们是perl内存泄漏最常被谈论和最佳记录形式。

我的经验是他们确实发生了。以下是我在使用Perl工作十年后看到的内存泄漏的快速概述。

我遇到了pTk应用程序开发泄漏的问题。我能够证明的一些泄漏是由于当Tk通过窗口参考时出现的循环引用。我也看到了pTk泄漏,其原因我永远无法追踪。

我看到人们误解了weaken并偶然结束了循环引用。

当太多经过深思熟虑的物品匆忙抛在一起时,我发现无意识的周期突然出现。

有一次,我发现内存泄漏来自正在创建大型深层数据结构的XS模块。我从来没有能够获得比整个程序更小的可重现的测试用例。但是当我用另一个串行器替换模块时,泄漏就消失了。所以我知道那些漏洞来自XS。

因此,根据我的经验,周期是泄漏的主要来源。

幸运的是,there is a module有助于追踪它们。

至于从未得到清理的大型全球结构是否构成“泄密”,我同意布莱恩的看法。他们像泄漏一样嘎嘎叫(由于一个bug,我们的进程内存使用量不断增长),所以它们是泄漏的。即便如此,我也记得在野外看不到这个特殊的问题。

根据我在巨石阵的网站上看到的内容,我猜brian看到了很多来自他正在训练或正在执行治疗性奇迹的人的代码。所以他的样本集比我的样本集更容易变化,但它有自己的选择偏差。

哪种泄漏原因最常见?我认为我们真的不知道。但我们都同意循环引用和全局数据垃圾是反模式,需要尽可能消除,并在少数情况下谨慎处理。

答案 1 :(得分:9)

如果问题出在Perl代码中,您可能有一个指向自身或父节点的引用。

通常它以对象的形式出现,引用父对象。

{ package parent;
  sub new{ bless { 'name' => $_[1] }, $_[0] }
  sub add_child{
    my($self,$child_name) = @_;
    my $child = child->new($child_name,$self);
    $self->{$child_name} = $child;   # saves a reference to the child
    return $child;
  }
}
{ package child;
  sub new{
    my($class,$name,$parent) = @_;
    my $self = bless {
      'name' => $name,
      'parent' => $parent # saves a reference to the parent
    }, $class;
    return $self;
  }
}
{
  my $parent = parent->new('Dad');
  my $child  = parent->add_child('Son');

  # At this point both of these are true
  # $parent->{Son}{parent} == $parent
  # $child->{parent}{Son}  == $child

  # Both of the objects **would** be destroyed upon leaving
  # the current scope, except that the object is self-referential
}

# Both objects still exist here, but there is no way to access either of them.

解决此问题的最佳方法是使用Scalar::Util::weaken

use Scalar::Util qw'weaken';
{ package child;
  sub new{
    my($class,$name,$parent) = @_;
    my $self = bless {
      'name' => $name,
      'parent' => $parent
    }, $class;

    weaken ${$self->{parent}};

    return $self;
  }
}

我建议尽可能从孩子那里删除父对象的引用。

答案 2 :(得分:5)

我以前遇到过XS问题,包括我自己的手工卷制和CPAN模块,如果管理不当,内存会从C代码中泄露出来。我从未设法追踪泄漏;该项目处于紧迫的截止日期,并且具有固定的运行生命周期,因此我每天cron重新启动,以解决问题。 cron真是太棒了。

答案 3 :(得分:2)

CPAN的一些模块使用循环引用来完成他们的工作,例如HTML::TreeBuilder(代表HTML树)。它们将要求你在最后运行一些破坏方法/例程。只需阅读文档:)