我在Perl代码库中追逐几个潜在的内存泄漏,我想知道Perl中有关内存(错误)管理的常见缺陷。
您在Perl代码中观察到的常见泄漏模式是什么?
答案 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树)。它们将要求你在最后运行一些破坏方法/例程。只需阅读文档:)