Какие операции могут (не) выбросить StackOverflowError?


Когда будет a StackOverError быть брошенным?

Или, скорее, когда это не будет брошено?

Для примера, если мы используем примитивные операторы +, +=, -, -=, == <, >, /, %, etc:

try {
     // operations +, +=, -, -=, == <, >, /, %, etc
} catch (java.lang.StackOverflowError e) {
     // will never occur?
}
Есть ли какая-то гарантия, что StackOverflowError не будет брошен?
5 4

5 ответов:

Верно ли, что код, который не вызывает никаких функций, никогда не бросит java.lang.StackOverflowError?

A StackOverflowError это-а VirtualMachineError. Оказывается, что нетникаких гарантий отсутствия броска оVirtualMachineErrors . Спецификация виртуальной машины Java говорит следующее (курсив добавлен).

Эта спецификация не может предсказать, где могут возникнуть внутренние ошибки или ограничения ресурсов. быть встречены и не предписывает точно, когда они могут быть сообщены. Таким образом, какой-нибудь из подклассов VirtualMachineError, определенных ниже, могут быть брошены в в любое время во время работы виртуальной машины Java: ... StackOverflowError

Теоретически, на некотором странном JVM + оператор может быть реализован с помощью рекурсии внутренне путем добавления + 1 в рекурсивный цикл. Другие операторы также могут быть реализованы с помощью внутреннего вызова метода.

Я не думаю, что это когда-нибудь произойдет. В каждой типичной архитектуре JVM / эти операции реализуются с использованием одной операции / инструкции. Для всех этих операторов существуют инструкции байт-кода, и я ожидаю, что они будут переведены 1:1 в сборку. Но их нет. гарантии в JLS.

Кстати Джавадок:

Выбрасывается, когда происходит переполнение стека, потому что приложение рекурсирует слишком глубоко.

Не совсем правильно. Вы можете получить StackOverflowError без рекурсии - если у вас очень глубокое дерево вызовов и методы имеют очень длинный список аргументов. Однако это очень трудно сделать на практике.

Единственной ссылкой на StackOverflowError в спецификации языка Java является следующая :

15.12.4.5 Создать Кадр, Синхронизировать, Передать Управление

Метод m в некотором классе S был определен как вызываемый.

Теперь создается новый фрейм активации, содержащий целевую ссылку (если таковая имеется) и значения аргументов (если таковые имеются), а также достаточно места для локальных переменных и стека для метода. вызывается и любая другая бухгалтерская информация, которая может потребоваться при реализации [...]. Если для создания такого фрейма активации недостаточно памяти, то выбрасывается StackOverflowError.

В спецификации JVM говорится следующее:

StackOverflowError: реализация виртуальной машины Java исчерпала пространство стека для потока, обычно потому, что поток выполняет неограниченное число рекурсивных вызовов в результате ошибки в исполняющаяся программа.

Итак, судя по приведенным выше утверждениям...

мне было интересно, правда ли, что код, который не вызывает никаких функций, никогда не бросит java.яз..StackOverflowError?

...да, это правда.

например, если я использую операторы +, +=, -, -=, ==, <, >, /, % и т.д. на примитивах (включая длинные и двойные),

Верно. Ни один из них будут ли они когда-либо (сами по себе) вызывать метод и, следовательно, не должны вызывать StackOverflowError.

Например, если я использую операторы +, +=, -, -=, == <, >, /, % и т.д. на примитивах (включая long и double)...Можем ли мы гарантировать, что ошибка stackoverflow не будет выброшена из этой операции?

Да, для стандартных операторов математики и сравнения. Ключ был там, где вы сказали "...о первобытных людях..." так как Java не допускает перегрузки операторов, и реализация JVM их для примитивов вы можете быть уверены, что это не повлечет за собой рекурсии. Это не обязательно будет иметь место, если вы говорите о непримитивах, где некоторые из этих операторов могут вызвать вызов кода, не являющегося JVM (например, с помощью + для добавления объекта к строке, что вызовет метод объекта toString).

Смотрите документацию:

Выбрасывается, когда происходит переполнение стека, потому что приложение рекурсирует слишком глубоко.

Таким образом, если ваше приложение вообще не рекурсирует (т. е. не вызывает методы), вы не получите StackOverflowError. Конечно, когда мы говорим не только о примитивах (где основные операции реализуются непосредственно с помощью инструкций Java bytecode ), мы легко сталкиваемся с StackOverflowErrors.

Пример:

int foo = 23;

foo = 23 + bar;

Что, если bar является java.lang.Integer? Java сделает автоматическую распаковку, и это приведет к следующему байт-коду:

   0:   bipush  23
   2:   istore_1
   3:   bipush  23
   5:   getstatic   #3; //Field bar:Ljava/lang/Integer;
        v v v v v v v 
   8:   invokevirtual   #4; //Method java/lang/Integer.intValue:()I
        ^ ^ ^ ^ ^ ^ ^
   11:  iadd
   12:  istore_1
   13:  return

Это (неявный) вызов метода и поэтому может вызвать StackOverflow.