Tôi đặt câu hỏi này để biết cách tăng kích thước ngăn xếp cuộc gọi thời gian chạy trong JVM. Tôi đã có câu trả lời cho điều này và tôi cũng có nhiều câu trả lời và nhận xét hữu ích liên quan đến cách Java xử lý tình huống cần một ngăn xếp thời gian chạy lớn. Tôi đã mở rộng câu hỏi của mình với bản tóm tắt các câu trả lời.
Ban đầu tôi muốn tăng kích thước ngăn xếp JVM để các chương trình như chạy mà không có a 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));
}
}
Cài đặt cấu hình tương ứng là java -Xss...
cờ dòng lệnh có giá trị đủ lớn. Đối với chương trình TT
trên, nó hoạt động như thế này với JVM của OpenJDK:
$ javac TT.java
$ java -Xss4m TT
Một trong những câu trả lời cũng đã chỉ ra rằng các -X...
cờ phụ thuộc vào việc triển khai. tôi đang sử dụng
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)
Cũng có thể chỉ định một ngăn xếp lớn chỉ cho một luồng (xem một trong các câu trả lời như thế nào). Điều này được khuyến khích hơn java -Xss...
để tránh lãng phí bộ nhớ cho các luồng không cần nó.
Tôi tò mò muốn biết chương trình phía trên cần một ngăn xếp lớn như thế nào, vì vậy tôi đã chạy nó n
tăng lên:
- -Xss4m có thể đủ cho
fact(1 << 15)
- -Xss5m có thể đủ cho
fact(1 << 17)
- -Xss7m có thể đủ cho
fact(1 << 18)
- -Xss9m có thể đủ cho
fact(1 << 19)
- -Xss18m có thể đủ cho
fact(1 << 20)
- -Xss35m có thể đủ cho
fact(1 << 21)
- -Xss68m có thể đủ cho
fact(1 << 22)
- -Xss129m có thể đủ cho
fact(1 << 23)
- -Xss258m có thể đủ cho
fact(1 << 24)
- -Xss515m có thể đủ cho
fact(1 << 25)
Từ những con số trên, có vẻ như Java đang sử dụng khoảng 16 byte trên mỗi khung ngăn xếp cho hàm trên, điều này là hợp lý.
Việc liệt kê ở trên có thể là đủ thay vì là đủ , bởi vì yêu cầu ngăn xếp là không xác định: chạy nó nhiều lần với cùng một tệp nguồn và giống nhau -Xss...
đôi khi thành công và đôi khi mang lại a StackOverflowError
. Ví dụ: 1 << 20, -Xss18m
là đủ trong 7 trong tổng số 10, và -Xss19m
không phải lúc nào cũng đủ, nhưng -Xss20m
là đủ (trong 100 lần hết 100). Có phải việc thu gom rác, JIT đang hoạt động hay thứ gì khác gây ra hành vi không xác định này không?
Dấu vết ngăn xếp được in tại a StackOverflowError
(và có thể cả các trường hợp ngoại lệ khác) chỉ hiển thị 1024 phần tử gần đây nhất của ngăn xếp thời gian chạy. Một câu trả lời dưới đây minh họa cách đếm độ sâu chính xác đạt được (có thể lớn hơn rất nhiều so với 1024).
Nhiều người đã phản hồi đã chỉ ra rằng đó là một phương pháp mã hóa tốt và an toàn khi xem xét các triển khai thay thế, ít tốn ngăn xếp hơn của cùng một thuật toán. Nói chung, có thể chuyển đổi thành một tập hợp các hàm đệ quy thành các hàm lặp (sử dụng một Stack
đối tượng ví dụ , được điền trên heap thay vì trên ngăn xếp thời gian chạy). Đối với fact
chức năng cụ thể này , nó khá dễ dàng để chuyển đổi nó. Phiên bản lặp lại của tôi sẽ giống như sau:
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, như giải pháp lặp ở trên cho thấy nó, fact
hàm không thể tính giai thừa chính xác của các số trên 65 (thực tế, thậm chí trên 20), bởi vì kiểu tích hợp sẵn của Java long
sẽ bị tràn. Cấu trúc lại fact
để nó sẽ trả về một BigInteger
thay vì long
cũng sẽ mang lại kết quả chính xác cho các đầu vào lớn.