代码改变世界

递归导致的StackOverflow的分析

2013-11-01 17:43  hengxinyi  阅读(3189)  评论(1编辑  收藏  举报

递归在多层次遍历时尤为重要,这里我们不讲递归的实现,来谈谈递归的内存占用情况。

如下代码,当我们运行时很简单,StackOverflowException瞬间抛出;这里确实是“瞬间”出错了,线程堆栈溢出;

首先我们要理解,一个程序是在一个进程下运行的,进程下可以有很多线程执行,但是每一个线程能占用的内存控件是有限的,大约1M,当一个线程占用超过1M时,就会StackOverflow了;存放在线程堆栈上的有方法中的值类型变量,和引用变量的指针地址。理解这些能帮我们分下,递归为什么导致StackOverflow了。

static void Main(string[] args)
{
      Main(args);
}

这里我们做一个简单的分析,不考虑不重要的因素:

首先,线程执行Main方法,首先方法总没有值类型变量,只有数据args,所有只有args的指针地址存放在线程堆栈上,占4byte,Main中有调用Main方法,如此递归调用,就相当于(4+4+4……+4)bytes,最终达到1M,导致StackOverflow异常。有CLR抛出异常。

当然这里线程执行方法只是一个简单的分析,实际情况很复杂,比如,进入方法,会有一个专门的表维护方法信息,方法的返回地址,参数,局部变量,这些都占用线程堆栈空间,方法返回时,所有方法占用的内存被释放。而上述递归,永远不存在方法返回,所以资源不会被释放,最终抛出异常。

 

如果说上述很简单理解,那我们加俩行代码:

 

static void Main(string[] args)
        {
            GC.Collect();
            GC.WaitForFullGCComplete();
            Main(args);
        }

这样执行方法,这个代码是我从某论坛的一个版主那里看来的,他说这样就不会导致StackOverflow了,结果下面的一群人深信不宜,我感觉递归肯定资源耗尽,只是GC不能回收线程堆栈内存,但是当我在代码上运行时,上述代码确实不报错,一直运行着,当时纳闷难道我理解错误,这不科学!我一直等着,果然过了1分钟多,我期待的StackOverflow出现了,心里窃喜。

但是我本身也不是很理解,为什么加了上面的两句话导致,要等那么久才移除,很奇怪。我问了问别人,结果实验了一下,将GC代码去掉,仅仅加一个Thread.Sleep(5),也会导致很长时间才抛出异常,费解中,最后我写了如下代码:

static int a = 0;
static void Main(string[] args)
        {
            a++;
            Console.WriteLine(++a);
            Main(args);
        }

我看下方法递归多少次,我电脑上是a=12万多,也就是最终方法递归了12万多次抛出了StackOverflow,所有上面的Thread.Sleep(5)执行长时间才报错也可以的出结论了,每一递归一次方法听5毫秒,递归1万次就是50秒,所以,结论不是GC回收影响递归长时间不报错,而是,每次执行GC,线程Sleep的时间,让我感觉长时间不报错。

 

上面问题本身没有多大意义,仅仅作为笔记记录一下,同样自己也更深入理解了内存占用。