【问题标题】:Perl memory management when overwriting objects覆盖对象时的 Perl 内存管理
【发布时间】:2015-08-12 17:21:02
【问题描述】:

我的问题是关于 Perl 如何在内部管理对象的数据。

在 Perl 中创建对象时,新的子例程通常会返回对祝福对象的引用。

以如下代码为例:

# Create a new object
my $object = Object->new(%data1);

# Create a new object with the same variable
$object = Object->new(%data2);

从第一次调用 new 开始,我们创建了一个 $object,它引用了一些祝福的 %data1

视觉表现:

“\”表示引用。

$object ════> \{bless %data1}

这在内存中看起来如下:

“&”表示地址

MEMORY:
----------------------------------
&{bless %data1} ════> bless %data1

然后在第二次调用 new 时,$object 的值被更改为引用其他一些受祝福的 %data2

视觉表现:

$object ══/ /══> \{bless %data1}  # The connection to %data1 is broken
   ║
   ╚═══════════> \{bless %data2}

现在的记忆应该是这样的:

MEMORY:
----------------------------------
&{bless %data1} ════> bless %data1
&{bless %data2} ════> bless %data2

现在的问题是$object 不再存储引用\{bless %data1}、地址&{bless %data1},并且存储在该地址的任何数据都将永远丢失。再也无法通过脚本访问存储在该位置的数据了。

我的问题是。 . .一旦对该数据的引用永远丢失,Perl 是否足够聪明地删除存储在 &{bless %data1} 中的数据,或者 Perl 是否会将这些数据保留在内存中可能导致内存泄漏?

【问题讨论】:

    标签: perl oop memory-management memory-leaks reference


    【解决方案1】:

    给定

    package Object {
       sub new { my $class = shift; bless({ @_ }, $class) }
    }
    
    my $object = Object->new( a => 1, b => 2 );
    

    在第二次分配之前,你有

               +============+   +==========+
    $object -->[ Reference ---->[ Blessed  ]
               +============+   [ Hash     ]
                                [          ]   +==========+
                                [ a: --------->[ 1        ]
                                [          ]   +==========+
                                [          ]
                                [          ]   +==========+
                                [ b: --------->[ 2        ]
                                [          ]   +==========+
                                +==========+
    

    (箭头代表指针。)

    Perl 使用引用计数来确定何时释放变量。作为赋值的一部分,当前由名称(引用)引用的变量的引用计数将递减,导致它被释放[1]。这将减少哈希的引用计数,导致它被释放[1]。这将减少值的引用计数,导致它们被释放[1]。


    在 Perl 中,当您有循环引用时会发生内存泄漏。

    {
       my $parent = Node->new();
       my $child = Node->new();
       $parent->{children} = [ $child ];
       $child->{parent} = $parent;
    }
    

    在退出区块之前,你已经

    $parent               +----------------------------------------------------+
     |                    |                                                    |
     |   +============+   +-->+==========+                                     |
     +-->[ Reference -------->[ Blessed  ]                                     |
         +============+       [ Hash     ]                                     |
                              [          ]   +==========+                      |
                              [ children --->[ Array    ]                      |
                              [          ]   [          ]   +============+     |
                              +==========+   [ 0: --------->[ Reference ----+  |
                                             [          ]   +============+  |  |
                                             +==========+                   |  |
                                                                            |  |
    $child                +-------------------------------------------------+  |
     |                    |                                                    |
     |   +============+   +-->+==========+                                     |
     +-->[ Reference -------->[ Blessed  ]                                     |
         +============+       [ Hash     ]                                     |
                              [          ]   +============+                    |
                              [ parent: ---->[ Reference ----------------------+
                              [          ]   +============+
                              +==========+
    

    存在区块后,你有

                          +----------------------------------------------------+
                          |                                                    |
                          +-->+==========+                                     |
                              [ Blessed  ]                                     |
                              [ Hash     ]                                     |
                              [          ]   +==========+                      |
                              [ children --->[ Array    ]                      |
                              [          ]   [          ]   +============+     |
                              +==========+   [ 0: --------->[ Reference ----+  |
                                             [          ]   +============+  |  |
                                             +==========+                   |  |
                                                                            |  |
                          +-------------------------------------------------+  |
                          |                                                    |
                          +-->+==========+                                     |
                              [ Blessed  ]                                     |
                              [ Hash     ]                                     |
                              [          ]   +============+                    |
                              [ parent: ---->[ Reference ----------------------+
                              [          ]   +============+
                              +==========+
    

    内存没有被释放,因为所有内容仍在被引用,因为存在引用循环。由于您无法访问此结构(没有变量名引用其中的任何内容),因此这是内存泄漏。


    1. 假设没有其他引用(指向)这些变量。

    【讨论】:

    • 很好的解释。一张好的 ASCII 艺术图值一千字 :)
    • @Borodin,你错了。 bless “告诉 REF 引用的东西它现在是 CLASSNAME 包中的一个对象。”使用perl -MDevel::Peek -e'Dump(bless(\%hash));' 和perl -e'bless(\%hash); (\%hash)->foo' 亲自查看。前者显示附加到散列的 OBJECT 标志和 STASH 字段,而不是参考。后者表明对象的类是已知的,即使使用的引用是在调用 bless 之后创建的。
    • @ikegami:是的,你当然是对的。我记得几年前我为自己解决了这个问题,但仍然有很多关于有福的参考的讨论,以至于我爱上了它。想必是因为bless带了引用,而bless %hash不行,但想必这只是bless的原型方便
    • 有趣,很高兴知道。我已经改写了我的问题,因此它不再将对象称为“有福的引用”。
    【解决方案2】:

    您误解了参数传递的工作方式。 $object 成为新创建的引用,其 内容 可能会受到传递给构造函数 new 的数据的影响,但它不会是对哈希 %data1 或 %data2 本身的引用因为new 只给出了这些哈希的键/值内容

    你的问题的底线似乎是 Perl 是否足够聪明,可以在不再使用对象时释放它们,答案是,是的

    Perl 保留对每个数据项的引用计数,如果该计数降至零(即不再有任何方法可以访问该数据),则该数据被认为可以重复使用

    Perl 可能导致内存泄漏的唯一情况是数据结构包含对其自身的引用。在这种情况下,外部引用的数量可能会降至零,但数据不会被自己的引用删除,从而使计数不会降至零

    避免使用包变量也更安全,只使用my 声明的词法变量。词法变量将在超出范围时自动销毁,从而减少它们可能包含的任何引用的计数。使用our 声明的包变量将在进程的整个生命周期中存在,并且不会触发此保护措施

    如果您再解释一下为什么需要此信息,那么我相信您会得到更好的答案

    【讨论】:

    • 我需要这些信息,因为我有创建对象并将其存储在变量中的代码,然后如果满足某些条件,它会使用相同的变量并创建一个新对象。这与问题中描述的情况基本相同。
    • @tjwrona1992:好的,但是你和成千上万其他不需要问这个问题的程序员处于同样的境地。您是否遇到内存泄漏?
    • 我使用它的方式,没有对自身的引用,所以我认为我是安全的。谢谢@Borodin
    • @tjwrona1992:避免使用包变量也更安全,只使用my声明的词法变量。词法变量将在超出范围时自动销毁,从而减少它们可能包含的任何引用的计数。使用our 声明的包变量将在进程的整个生命周期中存在,并且不会触发此保护措施
    • 我知道,我没有使用任何包变量。我只是想知道在引用丢失后对象是否会保留在内存中(即使在词汇上下文中也可能发生)
    【解决方案3】:

    Perl 使用一种称为引用计数的方法——它计算一个变量被引用的次数。它将数据保存在内存中,直到引用计数降至零。

    在您的示例中,一旦您重新分配$object,创建的第一个对象就会自动消失。但是有一个警告 - 如果 within 您的对象和您创建循环引用的 new 进程,则不会发生这种情况。您可以在Scalar::Util 中使用weaken 来处理这个问题。

    您可以通过创建DESTROY 方法来观察它,该方法在对象被“释放”时调用。

    【讨论】:

    • 很高兴知道。如果我遇到类似的循环引用情况,我会记住 Scalar::Util。
    【解决方案4】:

    有引用计数垃圾收集。我在您的代码中没有看到任何会绊倒的东西。即使有,Scalar::Util 以及其他选项也会减弱。

    【讨论】:

    • 您能详细说明一下吗?您似乎是在说这不是问题,但具体来说 Perl 如何以不会发生这种情况的方式处理内存?
    猜你喜欢
    • 2015-07-10
    • 2011-12-24
    • 2012-09-19
    • 1970-01-01
    • 2019-07-09
    • 2019-09-06
    • 2014-02-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多