ฉันถามคำถามนี้เพื่อทำความรู้จักวิธีเพิ่มขนาดรันไทม์ 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ให้ผลลัพธ์ที่แน่นอนสำหรับอินพุตขนาดใหญ่เช่นกัน