จะเพิ่มขนาด Java stack ได้อย่างไร?


124

ฉันถามคำถามนี้เพื่อทำความรู้จักวิธีเพิ่มขนาดรันไทม์ call stack ใน JVM ฉันมีคำตอบสำหรับเรื่องนี้และฉันยังมีคำตอบและความคิดเห็นที่เป็นประโยชน์มากมายที่เกี่ยวข้องกับวิธีที่ 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ด้านบนจะทำงานเช่นนี้กับ JVM ของ OpenJDK:

$ 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 ก็เพียงพอแล้วสำหรับ 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ก็เพียงพอใน 7 หมดจาก 10 และ-Xss19mก็ไม่เพียงพอเสมอไปเช่นกัน แต่-Xss20mก็เพียงพอ (ใน 100 ทั้งหมดจาก 100) การเก็บขยะ JIT เริ่มเข้ามาหรืออย่างอื่นทำให้เกิดพฤติกรรมที่ไม่เป็นไปตามข้อกำหนดนี้หรือไม่?

การติดตามสแต็กที่พิมพ์ที่ a 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));
  }
}

FYI ตามที่วิธีการแก้ปัญหาซ้ำ ๆ ด้านบนแสดงให้เห็นว่าfactฟังก์ชันนี้ไม่สามารถคำนวณแฟกทอเรียลที่แน่นอนของตัวเลขที่สูงกว่า 65 (จริงๆแล้วแม้จะสูงกว่า 20) เนื่องจาก Java ในตัวlongจะล้น การปรับโครงสร้างใหม่factดังนั้นมันจะส่งกลับ a BigIntegerแทนที่จะlongให้ผลลัพธ์ที่แน่นอนสำหรับอินพุตขนาดใหญ่เช่นกัน


ดูเรียบง่ายกว่าที่เป็นอยู่ fact () เรียกซ้ำ 32K ครั้ง ซึ่งควรมีขนาดน้อยกว่า 1MB ของสแต็ก : - /
Aaron Digulla

@Aaron: + Function overhead ซึ่งก็คือ .. a LOT
halfdan

4
นอกเหนือจากปัญหากองซ้อนของคุณ โปรดทราบว่าคุณกำลังระเบิดความยาวและ ints ของคุณ 1 << 4 คือค่าสูงสุดที่ฉันสามารถใช้ได้ก่อนที่จะลบแล้วเป็น 0 ลองใช้ BigInteger
Sean

ไม่แน่ใจว่าค่าใช้จ่ายของฟังก์ชันนั้นมีค่าใช้จ่ายมากแค่ไหน - ฉันคิดว่าคุณน่าจะยังโทรได้ 2 ^ 15 ตามลำดับพื้นที่สแต็กไม่กี่เมกะไบต์
Neil Coffey

7
หมายเหตุ: คุณกำลังตั้งค่าขนาดสแต็กของทุกเธรดและให้ผลลัพธ์ที่ไร้ความหมายทั้งหมดนี้เพื่อหลีกเลี่ยงการปรับโครงสร้างโค้ดบรรทัดเดียว ฉันดีใจที่คุณจัดลำดับความสำคัญของคุณได้ : P
Peter Lawrey

คำตอบ:


79

อืม ... มันใช้ได้สำหรับฉันและมีกองซ้อนน้อยกว่า 999MB:

> java -Xss4m Test
0

(Windows JDK 7, สร้าง 17.0-b05 ไคลเอนต์ VM และ Linux JDK 6 - ข้อมูลเวอร์ชันเดียวกับที่คุณโพสต์)


1
เป็นไปได้มากว่ามันเป็นความคิดเห็นของฉันฉันลบมันเมื่อฉันรู้สิ่งเดียวกับที่นีลโพสต์
Sean

ขอบคุณคำถามนี้และคำตอบของคุณฉันจัดการให้เสร็จสมบูรณ์ ฟังก์ชัน DFS ของฉันต้องเรียกคืนบนกราฟด้วยจุดยอด ~ 10 ^ 5 ในที่สุดก็ใช้ได้กับ -Xss129m: D
bholagabbar

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);
    }
}

9

หากคุณต้องการเล่นกับขนาดเธรดสแต็กคุณจะต้องดูที่ตัวเลือก -Xss บน Hotspot JVM อาจมีบางอย่างที่แตกต่างออกไปใน VM ที่ไม่ใช่ Hotspot เนื่องจากพารามิเตอร์ -X ไปยัง JVM เป็นการแจกจ่ายเฉพาะ IIRC

ใน Hotspot ดูเหมือนjava -Xss16Mว่าคุณต้องการสร้างขนาด 16 megs

พิมพ์java -X -helpหากคุณต้องการดูพารามิเตอร์ JVM เฉพาะการแจกจ่ายทั้งหมดที่คุณสามารถส่งผ่านได้ฉันไม่แน่ใจว่าจะใช้งานได้เหมือนกันกับ JVM อื่น ๆ หรือไม่ แต่จะพิมพ์พารามิเตอร์เฉพาะของ Hotspot ทั้งหมด

สิ่งที่คุ้มค่า - ฉันขอแนะนำให้ จำกัด การใช้วิธีการเรียกซ้ำใน Java การเพิ่มประสิทธิภาพนั้นไม่ดีเกินไปสำหรับหนึ่ง JVM ไม่รองรับการเรียกซ้ำหาง (ดูJVM ป้องกันการเพิ่มประสิทธิภาพการโทรหางหรือไม่ ) ลองเปลี่ยนรหัสแฟกทอเรียลของคุณด้านบนเพื่อใช้ while loop แทนการเรียกใช้วิธีการเรียกซ้ำ


8

Threadวิธีเดียวที่จะควบคุมขนาดของสแต็คที่อยู่ในกระบวนการคือการเริ่มต้นใหม่ แต่คุณยังสามารถควบคุมได้ด้วยการสร้างกระบวนการย่อย Java ที่เรียกเองด้วย-Xssพารามิเตอร์

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);
        }
    }

}

java -Xss...ขอบคุณสำหรับคำตอบข้อมูลนี้มันเป็นเรื่องดีที่จะรู้เกี่ยวกับตัวเลือกที่นอกเหนือไปจาก
pts

1
ฉันรู้สึกตื่นเต้นเกี่ยวกับเรื่องนี้ แต่หลังจากอ่านผ่านdocs.oracle.com/javase/6/docs/api/java/lang/Thread.html#Thread - ตัวสร้างstacksize - ความตื่นเต้นก็หายไป
kellogs

ฉันสงสัยว่าพวกเขาเป็นแพลตฟอร์มใดเมื่อเอกสารระบุเพียงว่า - "ในบางแพลตฟอร์ม"
Dennis C


2

เป็นการยากที่จะให้วิธีแก้ปัญหาที่สมเหตุสมผลเนื่องจากคุณกระตือรือร้นที่จะหลีกเลี่ยงวิธีการที่มีเหตุผลทั้งหมด การปรับโครงสร้างโค้ดหนึ่งบรรทัดใหม่เป็นวิธีการแก้ปัญหา

หมายเหตุ: การใช้ -Xss กำหนดขนาดสแต็กของทุกเธรดและเป็นความคิดที่แย่มาก

อีกวิธีหนึ่งคือการจัดการรหัสไบต์เพื่อเปลี่ยนรหัสดังนี้

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

ให้ทุกคำตอบสำหรับ n> 127 คือ 0 ซึ่งจะหลีกเลี่ยงการเปลี่ยนซอร์สโค้ด


1
ขอขอบคุณที่ชี้ให้เห็นว่าการตั้งค่าขนาดสแต็กที่สูงจะทำให้สิ้นเปลืองหน่วยความจำสำหรับเธรดที่ไม่ต้องการ นอกจากนี้ขอขอบคุณที่ชี้ให้เห็นว่าfactฟังก์ชันในคำถามสามารถปรับโครงสร้างใหม่เพื่อใช้พื้นที่สแต็กน้อยลงมาก
จุด

1
@pts ขอบคุณของคุณจะถูกบันทึกไว้ ฉันคิดว่านี่เป็นคำถามที่สมเหตุสมผลสำหรับกรณีการใช้งานที่ซับซ้อนกว่ามาก แต่ก็หายากมาก
Peter Lawrey

0

แปลก! คุณกำลังบอกว่าคุณต้องการสร้างการเรียกซ้ำ 1 << 15 ความลึก ??? !!!!

ฉันขอแนะนำว่าอย่าลองเลย 2^15 * sizeof(stack-frame)ขนาดของสแต็คจะเป็น ฉันไม่รู้ว่าขนาดสแต็กเฟรมคืออะไร แต่ 2 ^ 15 คือ 32.768 สวยมาก ... ถ้ามันหยุดที่ 1024 (2 ^ 10) คุณจะต้องทำให้มันใหญ่ขึ้น 2 ^ 5 เท่ามันใหญ่กว่าการตั้งค่าจริงของคุณ 32 เท่า


0

ผู้โพสต์อื่น ๆ ได้ชี้ให้เห็นถึงวิธีเพิ่มหน่วยความจำและคุณสามารถบันทึกการโทร ฉันขอแนะนำว่าสำหรับหลาย ๆ แอปพลิเคชันคุณสามารถใช้สูตรของ Stirling เพื่อประมาณค่า n ขนาดใหญ่! เร็วมากโดยแทบไม่เหลือรอยความทรงจำ

ลองดูที่โพสต์นี้ซึ่งมีการวิเคราะห์ฟังก์ชันและโค้ด:

http://threebrothers.org/brendan/blog/stirlings-approximation-formula-clojure/


0

ฉันทำAnagram excersizeซึ่งก็เหมือนกับปัญหาCount Changeแต่มี 50,000 นิกาย (เหรียญ) ฉันไม่แน่ใจว่ามันสามารถทำได้ซ้ำๆ ฉันไม่สนใจ ฉันเพิ่งรู้ว่าตัวเลือก -xss ไม่มีผลใด ๆ - ฉันมักจะล้มเหลวหลังจาก 1024 สแต็กเฟรมเสมอ (อาจเป็นเพราะสกาลาทำงานไม่ดีในการส่งไปยัง 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)

คุณจะเห็นว่าสแต็กสามารถขยายได้ลึกขึ้นแบบทวีคูณโดยมีการจัดเรียงสแต็กที่เพิ่มขึ้นแบบทวีคูณให้กับเธรด

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.