Я решил провести несколько экспериментов, чтобы узнать, что я могу узнать о размере фреймов стека и насколько далеко в стеке находится исполняемый в данный момент код. Здесь мы можем исследовать два интересных вопроса:
- На сколько уровней в стеке находится текущий код?
- Сколько уровней рекурсии может достичь текущий метод, прежде чем попадет в
StackOverflowError
?
Глубина стека текущего исполняемого кода
Вот лучшее, что я мог придумать для этого:
public static int levelsDeep() {
try {
throw new SomeKindOfException();
} catch (SomeKindOfException e) {
return e.getStackTrace().length;
}
}
Это кажется немного взломанным. Он генерирует и перехватывает исключение, а затем проверяет длину трассировки стека.
К сожалению, у него также есть фатальное ограничение, заключающееся в том, что максимальная длина возвращаемой трассировки стека составляет 1024. Все, что выходит за рамки этого, усекается, поэтому максимум, который может вернуть этот метод, равен 1024.
Вопрос:
Is there a better way of doing this that isn't so hacky and doesn't have this limitation?
Как бы то ни было, я предполагаю, что его нет: 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) результаты полностью согласуются: 9923 на моей машине (Ubuntu 14.04 64-бит). Но 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)
переполняется.
Вопрос:
Why is the maximum recursion depth non-deterministic on Oracle's Java 8? And why is it deterministic on OpenJDK 7?