2010-09-13 170 views
88

我問過這個問題,以瞭解如何增加JVM中的運行時調用堆棧大小。我已經得到了一個答案,並且我還得到了許多有用的答案和評論,這些答案和評論與Java如何處理需要大型運行時堆棧的情況相關。我已經回答了問題的總結。如何增加Java堆棧大小?

本來我想增加JVM堆棧的大小,所以像沒有StackOverflowError的程序運行。

public class TT { 
    public static long fact(int n) { 
    return n < 2 ? 1 : n * fact(n - 1); 
    } 
    public static void main(String[] args) { 
    System.out.println(fact(1 << 15)); 
    } 
} 

相應的配置設置是具有足夠大的值的java -Xss...命令行標誌。對於上面的程序TT,它的工作原理是這樣用的OpenJDK的JVM:

$ javac TT.java 
$ java -Xss4m TT 

答案之一還指出,-X...標誌是實現相關。我正在使用

java version "1.6.0_18" 
OpenJDK Runtime Environment (IcedTea6 1.8.1) (6b18-1.8.1-0ubuntu1~8.04.3) 
OpenJDK 64-Bit Server VM (build 16.0-b13, mixed mode) 

也可以爲一個線程指定一個大堆棧(請參閱其中一個答案)。建議使用java -Xss...以避免爲不需要它的線程浪費內存。

我很好奇,究竟上面的程序堆棧多麼大的需求,所以我已經運行n增加:

  • -Xss4m可以夠fact(1 << 15)
  • -Xss5m可以夠fact(1 << 17)
  • -Xss7m可以fact(1 << 18)足夠
  • -Xss9m可以爲fact(1 << 19)
  • -Xss18m可以連接足夠ough爲fact(1 << 20)
  • -Xss35m可以夠fact(1 << 21)
  • -Xss68m可以fact(1 << 22)
  • -Xss129m可以夠fact(1 << 23)
  • -Xss258m可以fact(1 << 24)
  • -Xss515m足夠可以夠足夠fact(1 << 25)

從上面的數字看來,Java似乎每個堆棧幀使用大約16個字節對於上面的功能來說,這是合理的。

以上枚舉包含可以足夠代替足夠,因爲堆棧的要求是不確定性:運行它多次具有相同的源文件和相同-Xss...有時成功並且有時產生一個StackOverflowError。例如。對於1 < < 20,-Xss18m已經足夠用於10次中的7次,並且-Xss19m也不總是足夠的,但是-Xss20m就足夠了(在全部100次中100次)。垃圾收集,JIT踢,或其他事情導致這種非確定性行爲?

打印在StackOverflowError(可能還有其他例外)的堆棧跟蹤僅顯示運行時堆棧的最新1024個元素。下面的答案演示瞭如何計算到達的確切深度(可能比1024大很多)。

許多回復的人指出,考慮替代的,較少堆棧的相同算法的實現是一種很好且安全的編碼實踐。一般情況下,也能夠將轉換爲一組遞歸函數來迭代函數(使用例如Stack對象,它被在堆上而不是在運行時堆棧填充)。對於這個特殊的fact功能,轉換它非常容易。我的迭代版本會是什麼樣子:

public class TTIterative { 
    public static long fact(int n) { 
    if (n < 2) return 1; 
    if (n > 65) return 0; // Enough powers of 2 in the product to make it (long)0. 
    long f = 2; 
    for (int i = 3; i <= n; ++i) { 
     f *= i; 
    } 
    return f; 
    } 
    public static void main(String[] args) { 
    System.out.println(fact(1 << 15)); 
    } 
} 

僅供參考,如上面的迭代求解顯示它的fact功能不能計算數字的65歲以上的確切因子(實際上,甚至高於20),因爲Java內置類型long會溢出。重構fact所以它會返回一個BigInteger代替long會產生精確的結果大投入爲好。

+0

看起來比它更簡單。 fact()被遞歸地調用32K次。這應該小於1MB的堆棧。 : -/ – 2010-09-13 12:51:08

+0

@Aaron:+函數開銷,這是..一個LOT – halfdan 2010-09-13 12:53:04

+4

除了你的堆棧問題。請注意,你正在炸燬你的長整數。 1 << 4是我在使用負數之前可以使用的最大值,然後變爲0.嘗試使用BigInteger – Sean 2010-09-13 13:36:00

回答

59

嗯......我的作品,並與遠小於棧的999MB:

> java -Xss4m Test 
0 

(視窗JDK 7,建設17.0-B05的客戶端虛擬機和Linux JDK 6 - 相同版本的信息,你發佈)

+1

很可能是我的評論,當我意識到Neil發佈的同樣的東西時,我刪除了它。 – Sean 2010-09-13 14:37:03

+0

感謝這個問題和你的回答,我設法完成了我的assigment。我的DFS函數必須在具有〜10^5個頂點的圖上遞歸。最後,它與-Xss129m:D – bholagabbar 2015-08-09 11:31:06

9

如果您想玩線程堆棧大小,您需要查看熱點JVM上的-Xss選項。由於到JVM的-X參數是特定於分佈的,IIRC,所以在非熱點虛擬機上可能會有所不同。

在Hotspot上,如果您想將大小設置爲16 megs,則看起來像java -Xss16M

類型java -X -help如果您想查看您可以傳入的所有特定於分發的JVM參數,我不確定這是否與其他JVM相同,但它會打印所有熱點特定參數。

對於它的價值 - 我會建議限制您在Java中使用遞歸方法。這不是在優化他們太大 - 一個在JVM不支持尾遞歸(見Does the JVM prevent tail call optimizations?)。嘗試重構上面的因子代碼以使用while循環而不是遞歸方法調用。

11

我假設你通過堆棧跟蹤中的循環線計算了「深度1024」?

顯然,在Throwable的堆棧跟蹤數組的長度似乎被限制在1024 試試下面的程序:

public class Test { 

    public static void main(String[] args) { 

     try { 
      System.out.println(fact(1 << 15)); 
     } 
     catch (StackOverflowError e) { 
      System.err.println("true recursion level was " + level); 
      System.err.println("reported recursion level was " + 
           e.getStackTrace().length); 
     } 
    } 

    private static int level = 0; 
    public static long fact(int n) { 
     level++; 
     return n < 2 ? n : n * fact(n - 1); 
    } 
} 
+4

+1一起用於實際深度檢查... – helios 2010-09-15 14:25:07

0

奇怪!你說你想生成一個遞歸1個< < 15深度 ??? !!!!的

我建議不要嘗試。堆棧的大小將爲2^15 * sizeof(stack-frame)。我不知道堆棧的大小是多少,但2^15是32.768。差不多......好吧,如果它停止在1024(2^10),你必須使它2^5倍大,它比你的實際設置大32倍。

+3

這是正確的,但它沒有回答我的問題。 – pts 2010-09-17 13:58:10

7

控制進程內堆棧大小的唯一方法是啓動新的Thread。但是您也可以通過使用-Xss參數創建一個自調用子Java進程來進行控制。

public class TT { 
    private static int level = 0; 

    public static long fact(int n) { 
     level++; 
     return n < 2 ? n : n * fact(n - 1); 
    } 

    public static void main(String[] args) throws InterruptedException { 
     Thread t = new Thread(null, null, "TT", 1000000) { 
      @Override 
      public void run() { 
       try { 
        level = 0; 
        System.out.println(fact(1 << 15)); 
       } catch (StackOverflowError e) { 
        System.err.println("true recursion level was " + level); 
        System.err.println("reported recursion level was " 
          + e.getStackTrace().length); 
       } 
      } 

     }; 
     t.start(); 
     t.join(); 
     try { 
      level = 0; 
      System.out.println(fact(1 << 15)); 
     } catch (StackOverflowError e) { 
      System.err.println("true recursion level was " + level); 
      System.err.println("reported recursion level was " 
        + e.getStackTrace().length); 
     } 
    } 

} 
+0

感謝您提供的信息豐富的答案,除了'java -Xss ...'以外,瞭解其他選項還是很好的。 – pts 2010-09-17 18:20:31

+1

我對此感到興奮,但隨後閱讀了http://docs.oracle.com/javase/6/docs/api/java/lang/Thread.html#Thread - stacksize構造函數 - 興奮消失了。 – kellogs 2013-12-09 13:53:42

+0

我不知道哪些平臺,他們當文檔只說 - 「在某些平臺上」 – 2014-12-05 01:02:22

1

由於您熱衷於避免所有理智的方法,因此很難給出明智的解決方案。重構一行代碼是可行的解決方案。

注意:使用-Xss設置每個線程的堆棧大小,這是一個非常糟糕的主意。

另一種方法是通過字節碼操作來更改代碼,如下所示;

public static long fact(int n) { 
    return n < 2 ? n : n > 127 ? 0 : n * fact(n - 1); 
} 

給出n> 127的每個答案都是0.這樣可以避免更改源代碼。

+1

感謝您指出設置高堆棧大小會浪費內存的線程不需要它。還要感謝您指出,問題中的「事實」函數可以重構爲使用更少的堆棧空間。 – pts 2010-09-17 22:14:47

+1

@pts,你的感謝是注意到的。我認爲在一個更復雜的用例中這是一個明智的問題,但這些非常罕見。 – 2010-09-18 14:46:40

0

我做Anagram excersize,這就好比Count Change問題,但有50種000面額(硬幣)。我是not sure that it can be done iteratively,我不在乎。我只知道-xss選項沒有任何作用 - 在1024個堆棧幀之後我總是失敗(可能是scala對java或printStackTrace的限制不好,但我不知道)。無論如何,這是不好的選擇。你不希望所有線程進入應用程序是可怕的。但是,我用新的線程(堆棧大小)做了一些實驗。這確實有效,

def measureStackDepth(ss: Long): Long = { 
    var depth: Long = 0 
     val thread: Thread = new Thread(null, new Runnable() { 
     override def run() { 
      try { 
      def sum(n: Long): Long = {depth += 1; if (n== 0) 0 else sum(n-1) + 1} 
      println("fact = " + sum(ss * 10)) 
      } catch { 
      case e: StackOverflowError => // eat the exception, that is expected 
      } 
     } 
     }, "deep stack for money exchange", ss) 
     thread.start() 
     thread.join() 
    depth 
    }            //> measureStackDepth: (ss: Long)Long 


    for (ss <- (0 to 10)) println("ss = 10^" + ss + " allows stack of size " -> measureStackDepth((scala.math.pow (10, ss)).toLong)) 
                //> fact = 10 
                //| (ss = 10^0 allows stack of size ,11) 
                //| fact = 100 
                //| (ss = 10^1 allows stack of size ,101) 
                //| fact = 1000 
                //| (ss = 10^2 allows stack of size ,1001) 
                //| fact = 10000 
                //| (ss = 10^3 allows stack of size ,10001) 
                //| (ss = 10^4 allows stack of size ,1336) 
                //| (ss = 10^5 allows stack of size ,5456) 
                //| (ss = 10^6 allows stack of size ,62736) 
                //| (ss = 10^7 allows stack of size ,623876) 
                //| (ss = 10^8 allows stack of size ,6247732) 
                //| (ss = 10^9 allows stack of size ,62498160) 

你會發現堆棧可以以指數級增長的指數級增長,並且指派更多的堆棧分配給該線程。

1

添加此選項

--driver-java的選項-Xss512m

到您的火花提交命令將解決這個問題。