@OverWrite BlogJava

            BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理 ::
            128 隨筆 :: 0 文章 :: 29 評論 :: 0 Trackbacks
          看兩段代碼:
          import java.util.ArrayList;   
          import java.util.List;   
            
          public class TailRecursionTest {   
              
          public static void main(String[] args) {   
                  TailRecursionTest t 
          = new TailRecursionTest();   
                  
          for (int i = 0; i < 10000; i++)   
                      t.a(
          0);   
              }
             
            
              
          public void a(int j) {   
                  j
          ++;   
                  List list 
          = new ArrayList<Integer>(100000);   
                  
          // 對list進行處理   
              }
             
          }

          沒啥特殊的,僅僅是為了測試,我們將a方法調用10000次,a方法創建一個有100000個元素的list的局部變量。
          第二個程序:
          import java.util.ArrayList;   
          import java.util.List;   
            
          public class TailRecursionTest2 {   
              
          public static void main(String[] args) {   
                  TailRecursionTest2 t 
          = new TailRecursionTest2();   
                  t.a(
          0);   
              }
             
            
              
          public void a(int j) {   
                  System.out.println(j);   
                  j
          ++;   
                  
          if (j == 10000)   
                      
          return;   
                  List list 
          = new ArrayList<Integer>(100000);   
                  
          // 對list進行處理   
                  a(j);   
              }
             
          }
            

          也沒啥特殊的,就是將循環換成了遞歸,a方法做的事情沒變。兩個都跑一下,程序1順利結束,程序2出問題了,啥問題?如下:
          161  
          162  
          163  
          164  
          165  
          Exception in thread 
          "main" java.lang.OutOfMemoryError: Java heap space   
              at java.util.ArrayList.
          <init>(Unknown Source)   
              at TailRecursionTest2.a(TailRecursionTest2.java:
          17)   
              at TailRecursionTest2.a(TailRecursionTest2.java:
          20)   
              at TailRecursionTest2.a(TailRecursionTest2.java:
          20)   
              at TailRecursionTest2.a(TailRecursionTest2.java:
          20)   
              at TailRecursionTest2.a(TailRecursionTest2.java:
          20

          我倒,才運行166次了,heap就滿了。問題在哪呢?oh,yep,你肯定想到了,是不是重復創建list這個大集合引起的呢?它不是局部變量嗎?怎么也會溢出?是的,list是局部變量,在a的方法棧里引用著,指向heap上的大對象,更關鍵的問題在于,java是沒有尾遞歸優化的,遞歸方法是不會使用同一個棧幀,每一次遞歸調用,都將壓入新的棧幀,并且這個棧幀上又new了一個list變量,引用著heap上新的一個大集合。隨著棧深度的增加, jvm里維持著一條長長的方法調用軌跡以便你能回來,在方法沒有返回之前,這些list變量一直被各自的棧幀引用著,不能被GC,你說,能不OOM嗎?

              也許,你想到了個補救方法來挽救程序2,就是每次在處理完list后,我把它設置為null,不讓棧幀繼續引用著它,咱編寫對gc友好的代碼,這不就行了,試試:


          import java.util.ArrayList;   
          import java.util.List;   
            
          public class TailRecursionTest2 {   
              
          public static void main(String[] args) {   
                  TailRecursionTest2 t 
          = new TailRecursionTest2();   
                  t.a(
          0);   
              }
             
            
              
          public void a(int j) {   
                  System.out.println(j);   
                  j
          ++;   
                  
          if (j == 10000)   
                      
          return;   
                  List list 
          = new ArrayList<Integer>(100000);   
                  
          // 對list進行處理   
                  list = null;  //gc友好   
                  a(j);   
              }
             
          }
           

          得意洋洋,我跑一下看看,這次跑到4000多次,但是:
             
          4289  
          4290  
          4291  
          4292  
          java.lang.StackOverflowError   
              at sun.nio.cs.ext.DoubleByteEncoder.encodeArrayLoop(Unknown Source)   
              at sun.nio.cs.ext.DoubleByteEncoder.encodeLoop(Unknown Source)   
              at java.nio.charset.CharsetEncoder.encode(Unknown Source) 

          總結:在java里,遞歸最好咱還是別用,老老實實地while、for;就算遞歸了,最好遞歸方法不要new太大的對象,除非你能確定遞歸的深度不是那么大,否則OOM和堆棧溢出的陰影將籠罩著你。
          posted on 2008-06-03 09:14 vesung 閱讀(1571) 評論(0)  編輯  收藏 所屬分類: Java
          主站蜘蛛池模板: 芒康县| 临安市| 绿春县| 江津市| 遵义县| 黎川县| 都安| 富民县| 密云县| 灌阳县| 隆德县| 突泉县| 华安县| 双牌县| 云阳县| 花垣县| 开平市| 桃江县| 阳江市| 淮北市| 含山县| 嘉义县| 柞水县| 思茅市| 仁布县| 慈利县| 绍兴县| 辽源市| 临泽县| 周口市| 新疆| 乐陵市| 大悟县| 南汇区| 开远市| 辽阳县| 龙山县| 吉首市| 江永县| 揭西县| 资中县|