TCO в Java — существует ли?
Спойлер: нет

Проходя собеседования наткнулась на интервьюера, который уверял, что в Java есть TCO начиная с 11 версии, показав такой вот код
try {
main(args);
} finally {
main(args);
}
Я конечно же настояла на своём, что не верю, на что мне был дан лишь один ответ: ну он работает и StackOverflowError не возникнет.
Я решила копнуть глубже, посмотрела байткод, а после на всякий случай ещё последила за стековым фреймом (ведь мы, как хорошие разработчики понимаем, что отсутствие ошибки не говорит об отсутствии проблемы), и что же я там увидела
// class version 68.0 (68)
// access flags 0x21
public class Main {
// compiled from: Main.java
// access flags 0x1
public <init>()V
L0
LINENUMBER 10 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
L1
LOCALVARIABLE this LMain; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x9
public static main([Ljava/lang/String;)V
TRYCATCHBLOCK L0 L1 L2 null
L0
LINENUMBER 35 L0
ALOAD 0
INVOKESTATIC Main.main ([Ljava/lang/String;)V
L1
LINENUMBER 37 L1
ALOAD 0
INVOKESTATIC Main.main ([Ljava/lang/String;)V
L3
LINENUMBER 38 L3
GOTO L4
L2
LINENUMBER 37 L2
ASTORE 1
ALOAD 0
INVOKESTATIC Main.main ([Ljava/lang/String;)V
L5
LINENUMBER 38 L5
ALOAD 1
ATHROW
L4
LINENUMBER 66 L4
RETURN
L6
LOCALVARIABLE args [Ljava/lang/String; L0 L6 0
MAXSTACK = 1
MAXLOCALS = 2
}
А где же оптимизация хвостовой рекурсии? Выглядит как обычная рекурсия, видим invoke внутри меток вместо разворачивания в цикл из изменений переменных и джампов на старт метода.
Следов оптимизации хвостовой рекурсии тут нет, пойдём смотреть что на стеке творится

Думаю слова тут излишни, и с каждой итерацией стек раздувается, а ошибки нет, да просто потому что мы не даём добраться до момента отброса ошибки, по факту это грязный хак 😸