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