Поскольку начальное состояние стека, а также влияние каждой инструкции на него хорошо известно, вы можете точно предсказать, какой тип элементов будет в стеке операндов в любое время:
[ ] // initially empty
[ I ] 0: iload_1
[ ] 1: ifne 6
[ I ] 4: iconst_1
[ ] 5: ireturn
[ I ] 6: iload_1
[ I O ] 7: aload_0
[ I O I ] 8: iload_1
[ I O I I ] 9: iconst_1
[ I O I ] 10: isub
[ I I ] 11: invokevirtual #2 // Method factorial:(I)I
[ I ] 14: imul
[ ] 15: ireturn
Верификатор JVM будет делать именно это, прогнозируя содержимое стека после каждой инструкции, чтобы проверить, подходит ли он в качестве ввода следующей инструкции. Но здесь полезно иметь объявленный максимальный размер, поэтому верификатору не нужно поддерживать динамически растущую структуру данных или предварительно выделять память для теоретически возможных записей стека 64 КБ. С объявленным максимальным размером он может остановиться при обнаружении команды, которая будет выдвигать больше, чем это, поэтому ему никогда не нужно больше памяти, чем объявлено.
Как видите, объявленный максимальный размер стека достигается ровно один раз, сразу после инструкции iconst_1
с индексом 9.
Это, однако, не означает, что компилятор должен будет выполнять такой анализ команд. Компилятор имеет модель более высокого уровня кода, полученного из исходного кода, известного как Абстрактное синтаксическое дерево .
Эта структура будет использоваться для генерации результирующего байт-кода, и она также может уже предсказывать требуемый размер стека на этом уровне. Но то, как на самом деле это делает компилятор, зависит от реализации.