Tại sao có thể khôi phục từ lỗi StackOverflowError?


100

Tôi ngạc nhiên về cách có thể tiếp tục thực hiện ngay cả sau khi StackOverflowError đã xảy ra trong Java.

tôi biết điều đó StackOverflowError là một phân loại của lớp Lỗi. Lớp Error được coi là "một lớp con của Throwable chỉ ra các vấn đề nghiêm trọng mà một ứng dụng hợp lý không nên cố gắng bắt."

Điều này nghe giống như một khuyến nghị hơn là một quy tắc, giả vờ rằng việc bắt một Lỗi như StackOverflowError trên thực tế được cho phép và tùy thuộc vào khả năng hợp lý của lập trình viên để không làm như vậy. Và hãy xem, tôi đã kiểm tra mã này và nó kết thúc bình thường.

public class Test
{
    public static void main(String[] args)
    {
        try {
            foo();
        } catch (StackOverflowError e) {
            bar();
        }
        System.out.println("normal termination");
    }

    private static void foo() {
        System.out.println("foo");
        foo();
    }

    private static void bar() {
        System.out.println("bar");
    }
}

Làm sao có thể? Tôi nghĩ vào thời điểm StackOverflowError được ném ra, ngăn xếp sẽ đầy đến mức không còn chỗ để gọi một hàm khác. Khối xử lý lỗi đang chạy trong một ngăn xếp khác hay điều gì đang xảy ra ở đây?



57
Tôi luôn mắc lỗi trên StackOverflow. Không ngăn cản tôi quay lại.

10
Yo dawg ... Tôi nghe nói rằng bạn thích tràn ngăn xếp, vì vậy chúng tôi đã đặt tràn ngăn xếp vào stackoverflow.com của bạn!
Pierre Henry

Bởi vì các kiến ​​trúc hiện đại sử dụng Frame Pointers để tạo điều kiện cho việc tháo dỡ các ngăn xếp, ngay cả những ngăn xếp từng phần. Miễn là mã + ngữ cảnh để làm điều đó không phải được cấp phát động ngoài ngăn xếp, thì sẽ không có vấn đề gì.
RBarryYoung

Câu trả lời:


119

Khi ngăn xếp bị tràn và StackOverflowErrorđược ném, xử lý ngoại lệ thông thường sẽ giải nén ngăn xếp. Mở ngăn xếp có nghĩa là:

  • hủy bỏ việc thực thi chức năng hiện đang hoạt động
  • xóa khung ngăn xếp của nó, tiếp tục với chức năng gọi
  • hủy bỏ việc thực thi người gọi
  • xóa khung ngăn xếp của nó, tiếp tục với chức năng gọi
  • và như thế...

... cho đến khi bắt được ngoại lệ. Điều này là bình thường (trên thực tế, cần thiết) và không phụ thuộc vào ngoại lệ nào được ném ra và tại sao. Vì bạn bắt được ngoại lệ bên ngoài lần gọi đầu tiên đến foo(), hàng nghìn fookhung ngăn xếp đã lấp đầy ngăn xếp đều chưa được liên kết và hầu hết ngăn xếp có thể được sử dụng lại miễn phí.


1
@fge Vui lòng chỉnh sửa, tôi đã xem xét ngắt đoạn nhưng không thể tìm thấy chỗ nào trông đẹp.

1
Bạn có thể sử dụng gạch đầu dòng ... Tôi không muốn chỉnh sửa bài đăng của người khác;)
fge

1
Vấn đề là phần trong cùng fookết thúc với trạng thái không xác định, vì vậy bất kỳ đối tượng nào mà nó có thể đã chạm vào phải được coi là bị hỏng. Vì bạn không biết hàm nào mà tràn ngăn xếp xảy ra, chỉ biết rằng nó phải là con của trykhối đã bắt nó, bất kỳ đối tượng nào có thể được sửa đổi bằng bất kỳ phương thức nào có thể truy cập được từ đó đều bị nghi ngờ. Thông thường, việc tìm hiểu điều gì đã xảy ra và cố gắng khắc phục nó là không đáng giá.
Simon Richter

2
@delnan, tôi nghĩ câu trả lời là không đầy đủ mà không đi sâu vào chi tiết tại sao đây là một ý tưởng tồi. Sự khác biệt với một ngoại lệ được ném rõ ràng là Errorkhông thể đoán trước được s ngay cả khi viết mã an toàn ngoại lệ.
Simon Richter

1
@SimonRichter Không, câu hỏi khá cụ thể. Nó không phải là về việc xử lý Errors. OP chỉ hỏi về StackOverflowErrorvà nó hỏi một điều cụ thể về việc xử lý lỗi này : làm thế nào để một cuộc gọi phương thức không bị lỗi khi mắc phải lỗi này.
Bakuriu

23

Khi StackOverflowError được ném ra, ngăn xếp đã đầy. Tuy nhiên, khi nó bị bắt , tất cả các foocuộc gọi đó đã được bật ra từ ngăn xếp. barcó thể chạy bình thường vì ngăn xếp không còn tràn foos. (Lưu ý rằng tôi không nghĩ rằng JLS đảm bảo bạn có thể khôi phục sau khi bị tràn ngăn xếp như thế này.)


12

Khi StackOverFlow xảy ra, JVM sẽ bật xuống phần bắt, giải phóng ngăn xếp.

Trong ví dụ của bạn, nó nhận được rids của tất cả foo xếp chồng lên nhau.


8

Bởi vì ngăn xếp không thực sự tràn. Tên tốt hơn có thể là AttemptToOverflowStack. Về cơ bản điều đó có nghĩa là nỗ lực cuối cùng để điều chỉnh khung ngăn xếp đã sai vì không còn đủ dung lượng trống trên ngăn xếp. Ngăn xếp thực sự có thể còn rất nhiều không gian, chỉ là không đủ. Vì vậy, bất kỳ hoạt động nào sẽ phụ thuộc vào cuộc gọi thành công (thường là một lệnh gọi phương thức), không bao giờ bị loại bỏ và tất cả những gì còn lại là để chương trình xử lý thực tế đó. Có nghĩa là nó thực sự không khác bất kỳ ngoại lệ nào khác. Trên thực tế, bạn có thể bắt được ngoại lệ trong hàm đang thực hiện cuộc gọi.


1
Chỉ cần cẩn thận nếu bạn làm điều này mà trình xử lý ngoại lệ của bạn không yêu cầu nhiều không gian ngăn xếp hơn khả dụng!
Vince

2

Như đã được trả lời , có thể thực thi mã, và cụ thể là gọi các hàm, sau khi bắt được a StackOverflowErrorvì thủ tục xử lý ngoại lệ bình thường của JVM giải nén ngăn xếp giữa hàm throwcatch giải phóng ngăn điểm, giải phóng không gian ngăn xếp cho bạn sử dụng. Và thử nghiệm của bạn xác nhận rằng đúng như vậy.

Tuy nhiên, điều đó không hoàn toàn giống với việc nói rằng, nói chung, có thể phục hồi từStackOverflowError .

A StackOverflowErrorIS-A VirtualMachineError, IS-AN Error. Như bạn đã chỉ ra, Java cung cấp một số lời khuyên mơ hồ cho Error:

chỉ ra các vấn đề nghiêm trọng mà một ứng dụng hợp lý không nên cố gắng bắt

và bạn, một cách hợp lý, kết luận rằng nên âm thanh như bắt một Errorcó thể là OK trong một số trường hợp. Lưu ý rằng việc thực hiện một thử nghiệm không chứng minh rằng điều gì đó nói chung là an toàn để thực hiện. Chỉ các quy tắc của ngôn ngữ Java và các đặc tả của các lớp bạn sử dụng mới có thể làm được điều đó. A VirtualMachineErrorlà một lớp ngoại lệ đặc biệt, bởi vì Đặc tả ngôn ngữ JavaĐặc tả máy ảo Java cung cấp thông tin về ngữ nghĩa của ngoại lệ này. Đặc biệt, sau này nói :

Một triển khai Máy ảo Java ném một đối tượng là một thể hiện của lớp con của lớp VirtualMethodError khi một lỗi nội bộ hoặc giới hạn tài nguyên ngăn cản nó triển khai ngữ nghĩa được mô tả trong chương này. Đặc điểm kỹ thuật này không thể dự đoán nơi có thể gặp phải các lỗi nội bộ hoặc các giới hạn về nguồn lực và không bắt buộc phải báo cáo chính xác khi nào chúng có thể được báo cáo. Do đó, bất kỳ VirtualMethodErrorlớp con nào được định nghĩa dưới đây có thể được ném vào bất kỳ lúc nào trong quá trình vận hành Máy ảo Java:

...

  • StackOverflowError: Việc triển khai Máy ảo Java đã hết không gian ngăn xếp cho một luồng, thường là do luồng đang thực hiện một số lượng lệnh gọi đệ quy không giới hạn do lỗi trong chương trình đang thực thi.

Vấn đề quan trọng là bạn "không thể đoán trước" vị trí hoặc khi nào một StackOverflowErrorsẽ được ném. Không có gì đảm bảo về nơi nó sẽ không bị ném. Bạn không thể dựa vào nó bị ném vào entry đến một phương pháp, ví dụ. Nó có thể được ném vào một điểm trong một phương thức.

Sự khó lường này tiềm ẩn rất nhiều tai họa. Vì nó có thể được ném bên trong một phương thức, nó có thể được ném từng phần thông qua một chuỗi hoạt động mà lớp coi là một hoạt động "nguyên tử", khiến đối tượng ở trạng thái đã sửa đổi một phần, không nhất quán. Với đối tượng ở trạng thái không nhất quán, bất kỳ nỗ lực nào để sử dụng đối tượng đó đều có thể dẫn đến hành vi sai lầm. Trong mọi trường hợp thực tế bạn không thể biết đối tượng đang ở trong một trạng thái không phù hợp, vì vậy bạn phải thừa nhận rằng không có đối tượng là đáng tin cậy. Do đó, bất kỳ hoạt động khôi phục nào hoặc nỗ lực tiếp tục sau khi bắt được ngoại lệ đều có thể có hành vi sai. Do đó, điều an toàn duy nhất cần làm là không bắtStackOverflowError, mà là để cho phép chương trình kết thúc. (Trong thực tế, bạn có thể cố gắng thực hiện một số lỗi ghi nhật ký để hỗ trợ khắc phục sự cố, nhưng bạn không thể dựa vào việc ghi nhật ký đó hoạt động chính xác). Đó là,bạn không thể phục hồi đáng tin cậy từ aStackOverflowError .

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.