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

kno*_*orv 14 perl memory-leaks

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

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

dao*_*oad 19

到目前为止,循环引用是泄漏的典型原因.

sub leak {
    my ($foo, $bar);
    $foo = \$bar;
    $bar = \$foo;
}
Run Code Online (Sandbox Code Playgroud)

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

在上面的示例代码,$foo$bar不再收集和每次调用后的副本将持续leak(),因为这两个变量有1引用计数.

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

use Scalar::Util qw(weaken);

sub dont_leak {
    my ($foo, $bar);
    $foo = \$bar;
    $bar = \$foo;
    weaken $bar;
}
Run Code Online (Sandbox Code Playgroud)

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

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

我的经验是他们确实发生了.这是我在使用Perl十多年后看到的内存泄漏的快速概述.

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

我看到人们误解weaken了,偶然发现了循环引用.

当太多经过深思熟虑的物体被匆忙抛到一起时,我已经看到无意的循环.

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

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

幸运的是,有一个模块可以帮助追踪它们.

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

根据我在巨石阵的网站上看到的内容,我猜布莱恩看到了很多来自他正在训练的人或者为他们制作治疗奇迹的病假代码.所以他的样本集比我的样本集更容易变化,但它有自己的选择偏差.

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

  • @brian d foy:严格来说,这不是内存泄漏,只是过多的内存使用.在程序*不能再释放内存之前,它不是内存*泄漏*. (4认同)
  • 有没有什么可以支持循环引用作为最常见的形式?我几乎没有看到这个问题,但我确实看到人们都有他们从未清楚过的全球性哈希. (2认同)

Bra*_*ert 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.
Run Code Online (Sandbox Code Playgroud)

解决这个问题的最好方法是使用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;
  }
}
Run Code Online (Sandbox Code Playgroud)

如果可能的话,我建议从子进程中删除对父对象的引用.

  • @daotoad:您不能使用析构函数来破坏循环引用,因为在引用计数降为零之前不会调用析构函数.如果调用析构函数,则没有循环引用问题.如果您有循环引用,则需要使用弱引用或在完成对象时手动调用析构函数方法(这很容易出错). (4认同)
  • 值得一提的是,对于对象,您还可以使用析构函数来破坏任何循环引用.你的Child类可能有'sub DESTROY {$ _ [0] - > {parent} = undef; }`,甚至`sub DESTROY {$ _ [0] = undef; }`并且不需要弱引用.弱参考是处理事物的更好方法,它们自动地做出正确的行为.值得注意的是,Moose具有属性的自动参考弱化:http://search.cpan.org/dist/Moose/lib/Moose/Manual/Attributes.pod#Weak_references这是Moose使OO Perl变得更好的另一种方式. (2认同)

ire*_*ses 5

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