Để mô tả điều này, trước tiên hãy cho chúng tôi hiểu cách các biến và đối tượng cục bộ được lưu trữ.
Biến cục bộ được lưu trữ trong ngăn xếp :
Nếu bạn nhìn vào hình ảnh, bạn sẽ có thể hiểu mọi thứ đang hoạt động như thế nào.
Khi một lệnh gọi hàm được gọi bởi một ứng dụng Java, một khung ngăn xếp được phân bổ trên ngăn xếp cuộc gọi. Khung ngăn xếp chứa các tham số của phương thức được gọi, các tham số cục bộ của nó và địa chỉ trả về của phương thức. Địa chỉ trả về biểu thị điểm thực hiện từ đó, việc thực hiện chương trình sẽ tiếp tục sau khi phương thức được gọi trả về. Nếu không có không gian cho khung ngăn xếp mới thì StackOverflowError
máy ảo Java (JVM) sẽ bị ném.
Trường hợp phổ biến nhất có thể làm cạn kiệt ngăn xếp của ứng dụng Java là đệ quy. Trong đệ quy, một phương thức gọi chính nó trong khi thực hiện. Đệ quy được coi là một kỹ thuật lập trình đa năng mạnh mẽ nhưng phải được sử dụng một cách thận trọng, để tránhStackOverflowError
.
Một ví dụ về ném a StackOverflowError
được hiển thị dưới đây:
StackOverflowErrorExample.java:
public class StackOverflowErrorExample {
public static void recursivePrint(int num) {
System.out.println("Number: " + num);
if (num == 0)
return;
else
recursivePrint(++num);
}
public static void main(String[] args) {
StackOverflowErrorExample.recursivePrint(1);
}
}
Trong ví dụ này, chúng tôi định nghĩa một phương thức đệ quy, được gọi là recursivePrint
in một số nguyên và sau đó, tự gọi nó, với số nguyên liên tiếp tiếp theo làm đối số. Đệ quy kết thúc cho đến khi chúng ta truyền vào 0
như một tham số. Tuy nhiên, trong ví dụ của chúng tôi, chúng tôi đã chuyển tham số từ 1 và theo dõi ngày càng tăng của nó, do đó, đệ quy sẽ không bao giờ chấm dứt.
Một thực thi mẫu, sử dụng -Xss1M
cờ chỉ định kích thước của ngăn xếp luồng bằng 1MB, được hiển thị bên dưới:
Number: 1
Number: 2
Number: 3
...
Number: 6262
Number: 6263
Number: 6264
Number: 6265
Number: 6266
Exception in thread "main" java.lang.StackOverflowError
at java.io.PrintStream.write(PrintStream.java:480)
at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:221)
at sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:291)
at sun.nio.cs.StreamEncoder.flushBuffer(StreamEncoder.java:104)
at java.io.OutputStreamWriter.flushBuffer(OutputStreamWriter.java:185)
at java.io.PrintStream.write(PrintStream.java:527)
at java.io.PrintStream.print(PrintStream.java:669)
at java.io.PrintStream.println(PrintStream.java:806)
at StackOverflowErrorExample.recursivePrint(StackOverflowErrorExample.java:4)
at StackOverflowErrorExample.recursivePrint(StackOverflowErrorExample.java:9)
at StackOverflowErrorExample.recursivePrint(StackOverflowErrorExample.java:9)
at StackOverflowErrorExample.recursivePrint(StackOverflowErrorExample.java:9)
...
Tùy thuộc vào cấu hình ban đầu của JVM, kết quả có thể khác nhau, nhưng cuối cùng StackOverflowError
sẽ bị ném. Ví dụ này là một ví dụ rất hay về cách đệ quy có thể gây ra vấn đề, nếu không được thực hiện một cách thận trọng.
Cách đối phó với StackOverflowError
Giải pháp đơn giản nhất là kiểm tra cẩn thận dấu vết ngăn xếp và phát hiện mẫu lặp lại của số dòng. Các số dòng này cho biết mã được gọi đệ quy. Khi bạn phát hiện các dòng này, bạn phải kiểm tra cẩn thận mã của mình và hiểu lý do tại sao đệ quy không bao giờ chấm dứt.
Nếu bạn đã xác minh rằng đệ quy được triển khai chính xác, bạn có thể tăng kích thước của ngăn xếp, để cho phép số lượng yêu cầu lớn hơn. Tùy thuộc vào Máy ảo Java (JVM) được cài đặt, kích thước ngăn xếp luồng mặc định có thể bằng 512KB hoặc 1MB . Bạn có thể tăng kích thước ngăn xếp luồng bằng -Xss
cờ. Cờ này có thể được chỉ định thông qua cấu hình của dự án hoặc thông qua dòng lệnh. Định dạng của
-Xss
đối số là:
-Xss<size>[g|G|m|M|k|K]