Что происходит в стеке?
Пытаюсь понять насколько много информации хранит в себе фрейм метода, не считая переменных, которые мы явно видим. Для этого я решил сделать следующее: Посмотреть насколько меньше раз влезет в стек метод с большим количеством переменных. Если точнее посчитать по этой формуле:
(meta+varaibles) * frames2 = meta * frames1 varaibles я бы контролировал сам, добавляя новые переменные во второй метод
frames1 и frames2 я просто по очереди вывожу на консоль
public static void var1(int count) {
try {
var1(count + 1);
}
catch (StackOverflowError e){
System.out.println(count);
}
}
public static void var2(int count) {
try {
//*здесь по задумке должно быть объявление какого-то количества переменных*
var2(count + 1);
}
catch (StackOverflowError e){
System.out.println(count);
}
}
Дальше решил проверить, возвращается ли мне 2 раза одно и то же число, когда я вызываю один и тот же метод, но увидел следующее:
public static void main(String... args) {
var1(0);
var1(0);
}
Вывод:
23540
1409314092140911409014089
14088
14087
14086
14085
14084
14083
14082
14081
14080
14079
14078
14077
14076
14075
14074
14073
14072
14071
1.Не понимаю, почему ломается вывод(StackOverFlow вызывается больше чем 1 раз?)
2.Даже не смотря на сломанный вывод можно заметить, что во втором случае у меня гораздо меньше свободного места
Объяснения этому найти не могу(есть предположение, что стек по какой-то причине меняет свой размер(?))
Ответы (1 шт):
Такой странный вывод объяснить просто. Во время работы System.out.println, уже после того как что-то вывелось (иногда не включая новую строку) случается новый StackOverflowError, который ловится на предыдущем вызове (поэтому числа уменьшаются на единицу).
Вместо вывода в блоке catch делайте return count, ну и если без исключения то тоже возвращайте то, что внутренний вызов вернул. И печатайте результат в main:
public class A {
public static int var1(int count) {
try {
return var1(count + 1);
} catch (StackOverflowError e) {
return count;
}
}
public static void main(String... args) {
for(int i = 0; i < 200; ++i) {
System.out.println(var1(0));
}
}
}
С параметрами по умолчанию на JVM в которой есть JIT, это выводит что-то типа:
19757
23817
23817
23817
23817
... и т.д. еще раз 20
39697
39697
... и дальше без изменений
Но если отключить JIT, то результат становится стабильным (и гораздо хуже):
9923
9923
9923
9923
... и т.д.
На java8 я это сделал добавлением опции -Djava.compiler=NONE при старте JVM. Для других версия смотрите тут https://stackoverflow.com/q/34315960/2231972
Размер стека не меняется, это умная среда исполнения перекомпилирует и модифицирует исполняемый код таким образом (например избавляется от использования переменных в стеке за счет регистровых переменных или встраивая вызовы функций так, что нет необходимости тратить место на фрейм стека и т.п.), что он может сделать больше вложенных вызовов.
Причем среда делает это не один раз, а постоянно анализирует исполнение кода, и в моем случае делала это дважды улучшив результат вдвое.
Возвращаясь к вопросу про размер фрейма, это все очень непросто. Одно дело, когда идет чисто интерпретация (когда JIT выключен), другое дело, когда есть еще компиляция (и перекомпиляция). Компилятор оптимизируюющий, да еще в динамике учитывая статистику исполнения и, как видно даже в простом примере, может делать очень сильные оптимизации, которые в том числе влияют на размер фрейма.