引用计数

Perl采用引用计数的方式来管理内存:每次引用内存数据,该数据的引用计数加1,当引用计数减为0,Perl将回收该内存数据。

具体来说,Perl会对每个内存数据都会维护一个引用计数器

  • 最初创建数据对象时,赋值给初始化变量或者保存在数组或hash中时,该数据的引用数为1
  • 以后每次引用、赋值引用、拷贝引用等操作都会对引用数加1
  • 将变量赋值为undef,或者,将数组或hash中原本指向该数据的元素设置为undef,将显式取消引用关系,引用数减1
  • 引用可能在作用域中,当离开对应作用域时,该作用域内的引用将取消(比如my声明的变量离开作用域时,引用数将1)
  • 只要某内存数据的引用计数还未减少为0,该内存数据所占用的内存就不会释放。当引用计数为0时,perl将回收这段内存空间,但不会交还给操作系统,在必要的时候perl会重用这段内存空间存储新数据,而无需再向操作系统申请新内存

例如:

my $name = "junma";     # 数据junma的引用数1
my @arr = (\$name, 23); # 数据junma的引用数2
my $name_ref = \$name;  # 数据junma的引用数3
{
  my $name_ref1 = $name_ref;  # 数据junma的引用数4
}                  # 数据junma的引用数3,出作用域
undef @arr=undef;  # 数据junma的引用数2
$name_ref = 111;  # 数据junma的引用数1
$name = undef;     # 数据junma的引用数0,junma被perl回收

使用引用计数方式管理内存,它最大的优点在于:

  • 即刻回收:只要数据对象的引用计数器为0了,就会立刻被回收,不会推迟
  • 暂停时长很短:因为回收时无需遍历内存,所以回收率很高

但使用引用计数方式管理内存也有很大的缺点:

  • 要频繁增、减计数,增、减计数的工作压力非常大
  • 无法回收循环引用

想象一下,在循环100W次的循环中使用一个字面量,那么增、减该数据的引用计数并回收该数据各100W次。好在,Perl已经对此做好了优化:在循环中会缓存字面量并在循环过程中一直使用该缓存结果。

say \"junma";
say \"junma";
for (1..5){
  say \"junma";
}

输出结果:

SCALAR(0x55e14a34dc10)  # 不在循环中,字面量地址不同
SCALAR(0x55e14a34dcd0)
SCALAR(0x55e14a7292b0)  # 循环过程中字面量地址相同
SCALAR(0x55e14a7292b0)
SCALAR(0x55e14a7292b0)
SCALAR(0x55e14a7292b0)
SCALAR(0x55e14a7292b0)