<commit_before>Perl语言再学习(3): 垃圾回收
===========================

Tag: 程序语言, perl

Date: 20130516

#### Perl的垃圾回收机制

之前一直没怎么关注过这个问题。
直到前两天线上出了个疑似内存泄露的事情，让我去查一查，才解除了相关的一些知识。
归结起来大致如下：

Perl的垃圾回收机制是在每次从一个block里退出时被触发的，比如：

	{
		my $var = "hello world";
		my $ret = func();
	}

这个code block可能是一个子函数，一个循环，一个if-else分支，甚至可能只是如上边这样的代码块。
当perl退出这样的block的时候会回收在代码块内部定义的变量。
但是有一个特殊情况，如果变量的`引用计数`不是0的话，则这个变量不会被回收。

于是就有了如下问题：

	{
		my ($a, $b) = @_;
		$a = \$b;
		$b = \$a;
	}

这样的代码由于`$a`和`$b`相互引用形成环，而使得*两个变量都没有被释放*。<br>
上边这个例子更简单的版本是这样的：

	{
		my $a;
		$a = \$a;
	}

Perl的垃圾回收方法十分*原始和暴力*——就是[mark-and-sweep](http://www.brpreiss.com/books/opus5/html/page424.html)<br>
因此如果在回收时，变量的引用计数不为0，则是不会被释放掉的。

一个解决方法是使用若引用`WeakRef`。
CPAN上有这个模块，这样的引用不会影响引用计数。
不过我觉得只要注意一下，倒是不必须使用这样的模块。

### 检测内存泄露的方法

尝试过几个库，[LeakTrace.pm](http://search.cpan.org/~gfuji/Test-LeakTrace-0.14/lib/Test/LeakTrace.pm)应该是最好用的一个。

下面是一个简单的例子：

	#/usr/bin/perl

	use strict;
	use Test::LeakTrace;

	sub memory_leak {
		my @array;
		$array[10000] = 1;

		my $ref = +{
			2 => "b",
			a => 1,
		};  
		
		# memory leak
		$ref->{c} = $ref;
	}

	my $cb = sub {
		my @caller = caller(3);
		print "Memory Leak Detected!! File: $caller[1] Line: $caller[2]\n";
	};

	sub main {
		eval {
			leaktrace {
				memory_leak();
			} \&$cb;
		};  
		if ($@) {
			print $@; 
		}   
	}

	&main();

当然，也有缺点，因为检测比较费时间，所以基本上线上跑这个比较困难。<br>
这对于大型项目来说自然不是什么好消息。<br>
不过好在这玩意开源，需要的话自己改吧~

<commit_msg>fix 20130516<commit_after>Perl语言再学习(3): 垃圾回收
===========================

Tag: 程序语言, perl

Date: 20130516

#### Perl的垃圾回收机制

之前一直没怎么关注过这个问题。
直到前两天线上出了个疑似内存泄露的事情，让我去查一查，才解除了相关的一些知识。
归结起来大致如下：

Perl的垃圾回收机制是在每次从一个block里退出时被触发的，比如：

	{
		my $var = "hello world";
		my $ret = func();
	}

这个code block可能是一个子函数，一个循环，一个if-else分支，甚至可能只是如上边这样的代码块。
当perl退出这样的block的时候会回收在代码块内部定义的变量。
但是有一个特殊情况，如果变量的`引用计数`不是0的话，则这个变量不会被回收。

于是就有了如下问题：

	{
		my ($a, $b) = @_;
		$a = \$b;
		$b = \$a;
	}

这样的代码由于`$a`和`$b`相互引用形成环，而使得**两个变量都没有被释放**。<br>
上边这个例子更简单的版本是这样的：

	{
		my $a;
		$a = \$a;
	}

当然，在程序退出的时候，Perl的会采用一种[mark-and-sweep](http://www.brpreiss.com/books/opus5/html/page424.html)的方法释放内存，可以彻底的释放掉所有使用的内存。<br>
所以对于CGI程序来说，以适当的间隔重启可以有效防止内存泄露吃光机器的内存。

另一个解决方法是使用弱引用`WeakRef`。
CPAN上有这个模块，这样的引用不会影响引用计数。
不过我觉得只要注意一下，倒是不必须使用这样的模块。

### 检测内存泄露的方法

尝试过几个库，[LeakTrace.pm](http://search.cpan.org/~gfuji/Test-LeakTrace-0.14/lib/Test/LeakTrace.pm)应该是最好用的一个。

下面是一个简单的例子：

	#/usr/bin/perl

	use strict;
	use Test::LeakTrace;

	sub memory_leak {
		my @array;
		$array[10000] = 1;

		my $ref = +{
			2 => "b",
			a => 1,
		};  
		
		# memory leak
		$ref->{c} = $ref;
	}

	my $cb = sub {
		my @caller = caller(3);
		print "Memory Leak Detected!! File: $caller[1] Line: $caller[2]\n";
	};

	sub main {
		eval {
			leaktrace {
				memory_leak();
			} \&$cb;
		};  
		if ($@) {
			print $@; 
		}   
	}

	&main();

当然，也有缺点，因为检测比较费时间，所以基本上线上跑这个比较困难。<br>
这对于大型项目来说自然不是什么好消息。<br>
不过好在这玩意开源，需要的话自己改吧~


{'fraction_non_alphanumeric': 0.18940443213296398, 'fraction_numerical': 0.022506925207756233, 'mean_word_length': 4.214801444043322, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6217573', 'n_tokens_mistral': 2111, 'n_tokens_neox': 2094, 'n_words': 185}