Какая опасность при рекурсивном вызове метода в Java
Что проверяют
Понимаете ли вы, как устроен стек вызовов в JVM и почему глубина рекурсии ограничена. Следом обычно спрашивают, поможет ли хвостовая рекурсия и как переписать алгоритм без неё.
Как отвечать
Главная опасность это StackOverflowError. Каждый вызов кладёт в стек потока фрейм с параметрами, локальными переменными и адресом возврата, а размер стека фиксирован. Задаётся он флагом -Xss (на 64-битном Linux по умолчанию 1 МБ) или параметром stackSize в конструкторе Thread. Сколько вызовов поместится, зависит от размера фрейма: от нескольких тысяч до десятков тысяч.
Разложите риски:
- нет базового случая или он недостижим: бесконечная рекурсия и падение потока;
- глубина зависит от данных: обход дерева или связного списка проходит тесты и падает на вырожденном дереве из боевых данных;
- экспоненциальное число вызовов: наивный Фибоначчи пересчитывает одно и то же, лечится мемоизацией или динамическим программированием;
- нет оптимизации хвостовых вызовов: HotSpot её не делает, так что хвостовая форма метода стек не сэкономит. В Kotlin есть модификатор
tailrec, который компилятор превращает в цикл, в Java аналога нет.
Способы защиты: итеративная версия с явным стеком на ArrayDeque вместо стека вызовов, ограничение глубины, мемоизация через HashMap. Отдельно назовите неявную рекурсию, которая встречается чаще написанной руками: двунаправленная связь JPA-сущностей плюс toString(), equals() и hashCode() от Lombok @Data, или сериализация такой пары через Jackson. Помогают @ToString.Exclude, пара @JsonManagedReference и @JsonBackReference или DTO вместо сущностей в ответе.
Где спотыкаются
Ловят StackOverflowError через catch (Exception e). Это Error, а не Exception, такой блок его не перехватит. Ловить его сознательно тоже плохая идея: после него состояние программы ненадёжно.
Предлагают просто увеличить -Xss. Это откладывает падение, но не решает проблему глубины, зависящей от данных, и увеличивает память на каждый поток.
Утверждают, что Java оптимизирует хвостовую рекурсию. Не оптимизирует, и это стоит сказать прямо.