Я решил попробовать несколько экспериментов, чтобы узнать, что я могу узнать о размере фреймов стека, и о том, как далеко через стек выполнялся текущий исполняемый код. Здесь есть два интересных вопроса:
- Сколько уровней в стеке является текущим кодом?
- Сколько уровней рекурсии может достичь текущего метода до того, как он достигнет значения
StackOverflowError
?
Глубина стека текущего исполняемого кода
Вот лучшее, что я мог бы придумать для этого:
public static int levelsDeep() {
try {
throw new SomeKindOfException();
} catch (SomeKindOfException e) {
return e.getStackTrace().length;
}
}
Это кажется немного взломанным. Он генерирует и ловит исключение, а затем смотрит, какая длина трассировки стека.
К сожалению, у него также есть фатальное ограничение, которое заключается в том, что максимальная длина возвращаемой трассировки стека равна 1024. Все, что выходит за рамки этого, опускается, поэтому максимум, который может вернуть этот метод, равен 1024.
Вопрос:
Есть ли лучший способ сделать это, что не так хаки и не имеет этого ограничения?
Для чего я думаю, что нет: Throwable.getStackTraceDepth()
- это родной вызов, который предлагает (но не доказывает), что это невозможно сделать в чистой Java.
Определение того, насколько мы оставили оставшуюся глубину рекурсии
Количество уровней, которые мы можем достичь, будет определяться (а) размером кадра стека и (б) количеством оставшегося стека. Давайте не будем беспокоиться о размере фрейма стека и просто посмотрим, сколько уровней мы можем достичь, прежде чем мы нажмем StackOverflowError
.
Здесь мой код для этого:
public static int stackLeft() {
try {
return 1+stackLeft();
} catch (StackOverflowError e) {
return 0;
}
}
Выполняет свою работу превосходно, даже если она линейна в количестве оставшихся стеков. Но вот очень, очень странная часть. На 64-разрядной версии Java 7 (OpenJDK 1.7.0_65) результаты совершенно согласуются: 9,923, на моей машине (Ubuntu 14.04 64-bit). Но Oracle Java 8 (1.8.0_25) дает мне недетерминированные результаты: я получаю записанную глубину где-то между примерно 18 500 и 20 700.
Теперь, почему это было бы недетерминированным? Там должен быть фиксированный размер стека, не так ли? И весь код выглядит детерминированным для меня.
Я задавался вопросом, было ли это что-то странное с улавливанием ошибок, поэтому я попробовал это вместо:
public static long badSum(int n) {
if (n==0)
return 0;
else
return 1+badSum(n-1);
}
Ясно, что это либо вернет введенный им ввод, либо переполнение.
Опять же, результаты, которые я получаю, не детерминированы на Java 8. Если я назову badSum(14500)
, он даст мне StackOverflowError
примерно в половине случаев и вернет 14500 другую половину. но на Java 7 OpenJDK он согласован: badSum(9160)
завершает штраф и badSum(9161)
переполнения.
Вопрос:
Почему максимальная глубина рекурсии не детерминирована на Oracle Java 8? И почему он детерминирован на OpenJDK 7?