Какую ошибку вызывает бесконечная рекурсия в Java
Что проверяют
Знаете ли вы, как устроен стек потока в JVM и чем Error отличается от исключения, которое принято ловить. Следом часто просят назвать неочевидные источники такой рекурсии в реальном коде и объяснить, почему не стоит просто увеличить стек.
Как отвечать
Бесконечная рекурсия заканчивается java.lang.StackOverflowError. Каждый вызов метода кладёт в стек потока фрейм с локальными переменными и адресом возврата. Размер стека фиксирован, и когда место кончается, JVM бросает ошибку в том потоке, где это случилось.
Что стоит добавить:
- иерархия:
StackOverflowErrorнаследуетVirtualMachineError, а таError. Это неException, в обычном коде его не ловят; - размер стека: задаётся флагом
-Xss(например,-Xss2m) или параметромstackSizeв конструктореThread. На 64-битном Linux по умолчанию обычно 1 МБ. Глубина до падения зависит от размера фреймов: от нескольких тысяч до десятков тысяч вызовов; - нет оптимизации хвостовой рекурсии: JVM не превращает хвостовой вызов в цикл, поэтому глубокую рекурсию переписывают итеративно или с явным стеком на
ArrayDeque; - последствия: непойманная ошибка завершает поток, а не обязательно всю программу. В веб-приложении падает обработка одного запроса.
Бонусом расскажите про рекурсию, которую никто не писал специально: toString, equals или hashCode у JPA-сущностей с двусторонней связью (особенно с Lombok @Data), сериализация таких сущностей через Jackson. Решение: @ToString.Exclude, пара @JsonManagedReference и @JsonBackReference, а лучше DTO вместо сущностей в ответе.
Где спотыкаются
Называют StackOverflowException. Такого класса в Java нет, это из C#.
Путают с OutOfMemoryError. Та в основном про кучу, а переполнение стека про память конкретного потока.
Предлагают лечить увеличением -Xss. Это откладывает падение, но не исправляет отсутствующее условие выхода или неограниченную глубину на больших данных.