遞歸在多層次遍歷時尤為重要,這里我們不講遞歸的實現,來談談遞歸的內存占用情況。
如下代碼,當我們運行時很簡單,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的時間,讓我感覺長時間不報錯。
上面問題本身沒有多大意義,僅僅作為筆記記錄一下,同樣自己也更深入理解了內存占用。