Почему максимальная глубина рекурсии, которую я могу достичь, недетерминирована?

я решил попробовать несколько экспериментов, чтобы узнать, что я могу узнать о размере кадров стека и как далеко через стек в настоящее время выполняется код. Есть два интересных вопроса, которые мы могли бы исследовать здесь:--11-->

  1. сколько уровней глубоко в стеке находится текущий код?
  2. сколько уровней рекурсии может достичь текущий метод, прежде чем он попадет в StackOverflowError?

глубина стека текущего выполнения код

4 ответов


наблюдаемое поведение зависит от оптимизатора HotSpot, однако это не единственная причина. Когда я запускаю следующий код

public static void main(String[] argv) {
    System.out.println(System.getProperty("java.version"));
    System.out.println(countDepth());
    System.out.println(countDepth());
    System.out.println(countDepth());
    System.out.println(countDepth());
    System.out.println(countDepth());
    System.out.println(countDepth());
    System.out.println(countDepth());
}
static int countDepth() {
    try { return 1+countDepth(); }
    catch(StackOverflowError err) { return 0; }
}

С JIT включен, я получаю такие результаты, как:

> f:\Software\jdk1.8.0_40beta02\bin\java -Xss68k -server -cp build\classes X
1.8.0_40-ea
2097
4195
4195
4195
12587
12587
12587

> f:\Software\jdk1.8.0_40beta02\bin\java -Xss68k -server -cp build\classes X
1.8.0_40-ea
2095
4193
4193
4193
12579
12579
12579

> f:\Software\jdk1.8.0_40beta02\bin\java -Xss68k -server -cp build\classes X
1.8.0_40-ea
2087
4177
4177
12529
12529
12529
12529

здесь эффект JIT хорошо виден, очевидно, оптимизированный код требует меньше пространства стека, и показано, что многоуровневая компиляция включена (действительно, используя -XX:-TieredCompilation показывает один прыжок, если программа работает достаточно долго).

напротив, с отключенным JIT я получаю следующие результаты:

> f:\Software\jdk1.8.0_40beta02\bin\java -Xss68k -server -Xint -cp build\classes X
1.8.0_40-ea
2104
2104
2104
2104
2104
2104
2104

> f:\Software\jdk1.8.0_40beta02\bin\java -Xss68k -server -Xint -cp build\classes X
1.8.0_40-ea
2076
2076
2076
2076
2076
2076
2076

> f:\Software\jdk1.8.0_40beta02\bin\java -Xss68k -server -Xint -cp build\classes X
1.8.0_40-ea
2105
2105
2105
2105
2105
2105
2105

значения по-прежнему различаются, но не в пределах одного потока выполнения и с меньшей величиной.

таким образом, существует (довольно небольшая) разница, которая становится намного больше, если оптимизатор может уменьшить пространство стека, необходимое для вызова метода, например, из-за вставки.

что может вызвать такую разницу? Я не знаю, как это делает JVM, но один сценарий может заключаться в том, что способ применения ограничения стека требует определенное выравнивание стека конец адрес (например, соответствующие размеры страниц памяти) в то время как память распределение возвращает память с начальным адресом, который имеет более слабую гарантию выравнивания. Объедините такой сценарий с ASLR и всегда может быть разница, в пределах размера требования к выравниванию.


Why is the maximum recursion depth non-deterministic on Oracle's Java 8? And why is it deterministic on OpenJDK 7?

об этом, возможно, связано с изменениями в сборке мусора. Java может выбрать другой режим для gc каждый раз. http://vaskoz.wordpress.com/2013/08/23/java-8-garbage-collectors/


это устарело, но вы можете попробовать Thread.countStackFrames() как

public static int levelsDeep() {
    return Thread.currentThread().countStackFrames();       
}

за Javadoc,

устаревший. определение этого вызова зависит от suspend(), который является устаревшим. Кроме того, результаты этого призыва никогда не были четко определены.

подсчитывает количество кадров стека в этой теме. Нить должна быть подвешена.

Что касается того, почему вы наблюдаете недетерминированный поведение, я могу только предположить, что это какая-то комбинация JIT и сборщика мусора.


вам не нужно ловить исключение, просто создайте такой:

новый Throwable().getStackTrace()

или:

нить.currentThread().getStackTrace()

Он по-прежнему хакерский, поскольку результат специфичен для реализации JVM. И JVM может решить обрезать результат для лучшей производительности.