StackOverflowError

Opis

W ramach pamięci wirtualnej maszyny Javy możemy wyróżnić m.in. takie obszary jak stos i sterta. Na stosie odkładane są ramki odpowiadające wywołaniu kolejnych metod w ramach danego wątku, natomiast na stertę trafiają tworzone w ramach aplikacji obiekty. StackOverflowError jest błędem, który występuje wtedy, gdy na stosie aktualnie wykonywanego wątku brakuje miejsca. Klasa błędu znajduje się w pakiecie java.lang , no i właśnie warto zwrócić uwagę na to, że jest to błąd, a nie wyjątek, tzn. klasa StackOverflowError nie dziedziczy ani po klasie Exception , ani RuntimeException.

Przykład

Błąd ten najczęściej występuje w sytuacji, gdy w ramach aplikacji wywoływana jest metoda rekurencyjna, która została zapisana w błędy sposób, czyli np. posiada błędnie zdefiniowany warunek stopu lub liczba wywołań rekurencyjnych jest tak duża, że na stosie po prostu brakuje miejsca. Do błędu można doprowadzić szczególnie łatwo w przypadku algorytmów charakteryzujących się wykładniczą złożonością obliczeniową, np. obliczaniem ciągu fibonacciego.

Do błędu można jednak doprowadzić także w przypadku prostej metody rekurencyjnej z liniową złożonością. Poniżej zapisana jest metoda, która oblicza sumę liczb z przedziału od 1 do N.

static int sum(int limit) {
    return limit >= 0 ? limit + sum(limit - 1) : 0;
}

Przykładowo dla argumentu 5 zwraca 15, ponieważ 5+4+3+2+1 = 15.

Kurs Java

Wywołanie tej metody dla argumentów takich jak 100, czy 1000 nie powinno stanowić problemu i w wyniku zobaczymy poprawne wydruki:

class StackOverflowExample {
    public static void main(String[] args) {
        int sum = sum(1000);
        System.out.println(sum);
    }

    static int sum(int limit) {
        return limit >= 1 ? limit + sum(limit - 1) : limit;
    }
}

Jeżeli jednak tę samą metodę wywołasz z dużo większym argumentem, np. 1_000_000

class StackOverflowExample {
    public static void main(String[] args) {
        int sum = sum(1_000_000);
        System.out.println(sum);
    }

    static int sum(int limit) {
        return limit >= 1 ? limit + sum(limit - 1) : limit;
    }
}

To uruchomienie programu zakończy się komunikatem błędu Exception in thread "main" java.lang.StackOverflowError.

stackoverflowerrorw consoli

Naprawa błędu

Sposobem na wyeliminowanie problemu jest najczęściej optymalizacja zapisanego algorytmu. W Javie niestety nie można wykorzystać rekurencji ogonowej w celu ograniczenia rozmiaru stosu, dlatego jedynym sposobem na rozwiązanie problemu jest przerobienie wywołań rekurencyjnych na klasyczną formę iteracyjną. Zapisując wcześniejszy kod w taki sposób:

static int sum(int limit) {
    int sum = 0;
    for (int i = 1; i <= limit; i++) {
        sum += i;
    }
    return sum;
}

lub

static int sum(int limit) {
    return IntStream.rangeClosed(1, limit).sum();
}

Metoda zwróci wynik dla dowolnie dużego argumentu.

Dodatkowe źródła

Dokumentacja klasy StackOverflowError: https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/lang/StackOverflowError.html

Dyskusja i komentarze

Masz pytania do tego wpisu? Może chcesz się podzielić spostrzeżeniami? Zapraszamy dyskusji na naszej grupie na Facebooku.