Stack khó xử lý lỗi

StackOverflowError trong Java

Người dịch: Nguyễn Thành Trung - Học viên lớp Java08
Email liên hệ:
Bài viết gốc: //www.baeldung.com/java-stack-overflow-error

1. Tổng quan

StackOverflowError có thể gây khó chịu cho các nhà phát triển Java, vì đây là một trong những lỗi runtime phổ biến nhất mà chúng ta có thể gặp phải.

Trong bài viết này, chúng ta sẽ xem lỗi này có thể xảy ra như thế nào bằng cách xem xét nhiều ví dụ khác nhau cũng như cách xử lý nó.

2. Stack Frames và StackOverflowError xảy ra ra sao

Hãy bắt đầu với những điều cơ bản. Khi một phương thức được gọi, một stack frame mới sẽ được tạo trên call Stack. Stack frame này chứa các tham số của phương thức được gọi, các biến cục bộ của nó và địa chỉ trả về của phương thức, tức là điểm mà từ đó việc thực thi phương thức sẽ tiếp tục sau khi phương thức được gọi đã trả về.

Việc tạo các Stack frame sẽ tiếp tục cho đến khi nó kết thúc các lệnh gọi phương thức được tìm thấy bên trong các phương thức lồng nhau.

Trong quá trình này, nếu JVM gặp phải tình huống không có không gian để tạo stack frame mới, nó sẽ tạo ra lỗi StackOverflowError.

Nguyên nhân phổ biến nhất khiến JVM gặp phải tình huống này là đệ quy không kết thúc / vô hạn - mô tả Javadoc cho StackOverflowError đề cập rằng lỗi được tạo ra do đệ quy quá sâu trong một đoạn mã cụ thể.

Tuy nhiên, đệ quy không phải là nguyên nhân duy nhất gây ra lỗi này. Nó cũng có thể xảy ra trong tình huống ứng dụng tiếp tục gọi các phương thức từ bên trong các phương thức cho đến khi hết ngăn xếp. Đây là một trường hợp hiếm hoi vì không có nhà phát triển nào cố tình làm theo các phương pháp mã hóa xấu. Một nguyên nhân hiếm gặp khác là có một số lượng lớn các biến cục bộ bên trong một phương thức.

StackOverflowError cũng có thể được ném ra khi một ứng dụng được thiết kế để có mối quan hệ tuần hoàn giữa các lớp . Trong tình huống này, các hàm tạo của nhau được gọi lặp đi lặp lại, điều này gây ra lỗi này. Đây cũng có thể coi là một dạng đệ quy.

Một tình huống thú vị khác gây ra lỗi này là nếu một lớp đang được khởi tạo trong cùng một lớp với một biến thể hiện của lớp đó . Điều này sẽ làm cho hàm tạo của cùng một lớp được gọi đi gọi lại [đệ quy] và cuối cùng dẫn đến lỗi StackOverflowError.

Trong phần tiếp theo, chúng ta sẽ xem xét một số ví dụ chứng minh những tình huống này.

3. StackOverflowError đang hoạt động

Trong ví dụ được hiển thị bên dưới, một StackOverflowError sẽ được ném ra do đệ quy ngoài ý muốn, trong đó nhà phát triển đã quên chỉ định điều kiện kết thúc cho hành vi đệ quy:

public class UnintendedInfiniteRecursion {
    public int calculateFactorial[int number] {
        return number * calculateFactorial[number - 1];
    }
}

Ở đây, lỗi được ném vào tất cả các trường hợp cho bất kỳ giá trị nào được truyền vào phương thức:

public class UnintendedInfiniteRecursionManualTest {
    @Test[expected = StackOverflowError.class]
    public void givenPositiveIntNoOne_whenCalFact_thenThrowsException[] {
        int numToCalcFactorial= 1;
        UnintendedInfiniteRecursion uir 
          = new UnintendedInfiniteRecursion[];
        
        uir.calculateFactorial[numToCalcFactorial];
    }
    
    @Test[expected = StackOverflowError.class]
    public void givenPositiveIntGtOne_whenCalcFact_thenThrowsException[] {
        int numToCalcFactorial= 2;
        UnintendedInfiniteRecursion uir 
          = new UnintendedInfiniteRecursion[];
        
        uir.calculateFactorial[numToCalcFactorial];
    }
    
    @Test[expected = StackOverflowError.class]
    public void givenNegativeInt_whenCalcFact_thenThrowsException[] {
        int numToCalcFactorial= -1;
        UnintendedInfiniteRecursion uir 
          = new UnintendedInfiniteRecursion[];
        
        uir.calculateFactorial[numToCalcFactorial];
    }
}

Tuy nhiên, trong ví dụ tiếp theo, một điều kiện kết thúc được chỉ định nhưng sẽ không bao giờ được đáp ứng nếu giá trị -1 được chuyển đến phương thức calculateFactorial[] , điều này gây ra đệ quy không kết thúc / vô hạn:

public class InfiniteRecursionWithTerminationCondition {
    public int calculateFactorial[int number] {
       return number == 1 ? 1 : number * calculateFactorial[number - 1];
    }
}

Tổng hợp các bài kiểm tra này thể hiện tình huống này:

public class InfiniteRecursionWithTerminationConditionManualTest {
    @Test
    public void givenPositiveIntNoOne_whenCalcFact_thenCorrectlyCalc[] {
        int numToCalcFactorial = 1;
        InfiniteRecursionWithTerminationCondition irtc 
          = new InfiniteRecursionWithTerminationCondition[];

        assertEquals[1, irtc.calculateFactorial[numToCalcFactorial]];
    }

    @Test
    public void givenPositiveIntGtOne_whenCalcFact_thenCorrectlyCalc[] {
        int numToCalcFactorial = 5;
        InfiniteRecursionWithTerminationCondition irtc 
          = new InfiniteRecursionWithTerminationCondition[];

        assertEquals[120, irtc.calculateFactorial[numToCalcFactorial]];
    }

    @Test[expected = StackOverflowError.class]
    public void givenNegativeInt_whenCalcFact_thenThrowsException[] {
        int numToCalcFactorial = -1;
        InfiniteRecursionWithTerminationCondition irtc 
          = new InfiniteRecursionWithTerminationCondition[];

        irtc.calculateFactorial[numToCalcFactorial];
    }
}

Trong trường hợp cụ thể này, lỗi hoàn toàn có thể tránh được nếu điều kiện chấm dứt đơn giản là:

public class RecursionWithCorrectTerminationCondition {
    public int calculateFactorial[int number] {
        return number 

Bài Viết Liên Quan

Chủ Đề