Elettracompany.com

Компьютерный справочник
0 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

Java lang stackoverflowerror null

StackOverFlow Error: Causes and Solutions

Want to learn more about the potential causes and solutions to the StackOverFlowError in your JVM project? Check out this post to learn more.

Join the DZone community and get the full member experience.

StackOverFlowError is one of the common confronted JVM errors. In this blog post, we will look at the inner mechanics of thread stacks, reasons that can trigger StackOverFlowError, and potential solutions to address this error.

To gain deeper understanding into StackOverFlowError, let’s review this simple program:

This program is very simple with the following execution code:

  1. main() method is invoked first
  2. main() method invokes a() method. Inside a() method, the integer variable ‘x’ is initialized to value 0.
  3. a() method in turn invokes b() method. Inside b() method, the Car object is constructed and assigned to variable ‘y.’
  4. b() method in turn invokes the c() method. Inside the c() method, the float variable ‘z’ is initialized to value 0.

Now, let’s review what happens behind the scenes when above simple program is executed. Each thread in the application has its own stack. Each stack has multiple stack frames. The thread adds the methods it’s executing, primitive data types, object pointers, and return values to its stack frame in the sequence order in which they are executed.

Fig 1: Thread’s Stack frame

Step #1: main() method is pushed into the application thread’s stack.

Step #2: a() method is pushed into application thread’s stack. In a() method, primitive data type ‘int’ is defined with value 0 and assigned to variable x. This information is also pushed into the same stack frame. Note that both data, i.e. ‘0’ and variable ‘x,’ is pushed into thread’s stack frame.

Step #3: b() method is pushed into thread’s stack. In the b() method, the Car object is created and assigned to variable ‘y.’ A crucial point to note here is that the ‘Car’ object is created in the heap and not in the thread’s stack. Only the Car object’s reference, i.e. y, is stored in the thread’s stack frame.

Step #4: c() method is pushed into thread’s stack. In c() method, primitive data type ‘float’ is defined with value 0f and assigned to variable z. This information is also pushed into same stack frame. Note both data, i.e. ‘0f’ and variable ‘z,’ is pushed into thread’s stack frame.

Once each method’s execution is completed, then the method and the variables/object pointers are stored in the stack frame are removed, as shown in Fig 2.

Fig 2: Thread’s stack frame after executing methods

What Causes StackOverflowError?

As you can see, thread’s stack is storing methods it’s executing, primitive datatypes, variables, object pointers, and return values. All of these consume memory. If thread’s stack sizes grow beyond the allocated memory limit, then StackOverflowError is thrown. Let’s look at the below buggy program, which will result in a StackOverflowError :

In this program, the main() method invokes a() method. a() method recursively calls itself. This implementation will cause a() method to be invoked infinite number of times. In this circumstance, a() method will be added to thread’s stack frame infinite number of times. Thus, after a few thousand iterations, thread’s stack size limit would be exceeded. Once stack size limit is exceeded, it will result in StackOverflowError :

Fig 3: StackOverflowError progression

What Are the Solutions to StackOverflowError?

There are couple of strategies to address StackOverflowError .

1. Fix the Code

Because of a non-terminating recursive call (as shown in the above example), threads stack size can grow to a large size. In those circumstances, you must fix the source code that is causing recursive looping. When ‘StackOverflowError’ is thrown, it will print the stacktrace of the code that it was recursively executing. This code is a good pointer to start debugging and fixing the issue. In the above example, it’s the a() method.

2. Increase Thread Stack Size (-Xss)

There might be legitimate reason where a threads stack size needs to be increased. Maybe thread has to execute large number of methods or lot of local variables/created in the methods thread has been executing? In such circumstances, you can increase the thread’s stack size using the JVM argument: ‘-Xss.» This argument needs to be passed when you start the application. Example:

This will set the thread’s stack size to 2 mb.

It might bring a question: what is the default thread’s stack size? Default thread stack size varies based on your operating system, Java version, and vendor.

Что такое StackOverflowError?

355 Ziggy [2008-10-18 11:13:00]

Что такое StackOverflowError , что его вызывает, и как мне с ними бороться?

java exception-handling stack-overflow

13 ответов

331 Решение Sean [2008-10-18 11:34:00]

Параметры и локальные переменные выделяются в стеке (с ссылочными типами объект живет в куче, а переменная ссылается на этот объект). Стек обычно находится в верхнем конце вашего адресного пространства, и, поскольку он используется, он направляется к нижней части адресного пространства (т.е. к нулю).

У вашего процесса также есть куча, которая живет в нижней части вашего процесса. Когда вы выделяете память, эта куча может расти в верхнем конце вашего адресного пространства. Как вы можете видеть, существует вероятность того, что куча «столкнется» со стеклом (немного как тектонические пластины . ).

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

Однако при программировании графического интерфейса возможно генерировать непрямую рекурсию. Например, ваше приложение может обрабатывать сообщения с краской, а при их обработке оно может вызывать функцию, которая заставляет систему отправлять другое сообщение с краской. Здесь вы явно не называли себя, но OS/VM сделала это для вас.

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

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

74 Varun [2015-03-26 16:06:00]

Чтобы описать это, сначала давайте понять, как хранятся локальные переменные и объекты.

Локальная переменная хранится в стеке:

Если вы посмотрите на изображение, вы сможете понять, как все работает.

Когда вызов функции вызывается Java-приложением, стек стека выделяется в стеке вызовов. Фрейм стека содержит параметры вызываемого метода, его локальные параметры и обратный адрес метода. Адрес возврата обозначает точку выполнения, из которой выполнение программы должно продолжаться после возврата вызванного метода. Если нет места для нового стека кадров, StackOverflowError вызывается виртуальной машиной Java (JVM).

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

Читать еще:  Java util inputmismatchexception

Пример, показывающий StackOverflowError , показан ниже:

StackOverflowErrorExample.java:

В этом примере мы определяем рекурсивный метод, называемый recursivePrint который печатает целое число и затем вызывает себя со следующим последовательным целым числом в качестве аргумента. Рекурсия заканчивается, пока мы не перейдем в 0 как параметр. Однако в нашем примере мы передали параметр от 1 и его возрастающих последователей, поэтому рекурсия никогда не закончится.

Пример выполнения примера с использованием флага -Xss1M который определяет размер стека потоков равным 1 МБ, показан ниже:

В зависимости от начальной конфигурации JVM результаты могут отличаться, но в конечном итоге следует вызывать StackOverflowError . Этот пример — очень хороший пример того, как рекурсия может вызывать проблемы, если их не применять с осторожностью.

Как бороться с StackOverflowError

Простейшим решением является тщательная проверка трассировки стека и обнаружение повторяющегося шаблона номеров строк. Эти номера строк указывают, что код рекурсивно называется. Как только вы обнаружите эти строки, вы должны тщательно проверить свой код и понять, почему рекурсия никогда не заканчивается.

Если вы подтвердили правильность реализации рекурсии, вы можете увеличить размер стеков, чтобы разрешить большее количество вызовов. В зависимости от установленной виртуальной машины Java (JVM) размер стека по умолчанию может равняться либо 512 КБ, либо 1 МБ. Вы можете увеличить размер стека потоков, используя флаг -Xss . Этот флаг может быть указан либо через конфигурацию проектов, либо через командную строку. Формат аргумента -Xss : -Xss [g|G|m|M|k|K]

61 Khoth [2008-10-18 11:31:00]

Если у вас есть такая функция, как:

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

23 Cheery [2008-10-18 13:06:00]

Переполнение стека означает, что: переполнение стека. Обычно в программе есть один стек, содержащий переменные и адреса локальной области, в которые нужно вернуться, когда выполнение процедуры заканчивается. Этот стек имеет тенденцию быть фиксированным диапазоном памяти где-то в памяти, поэтому он ограничивает, сколько он может содержать значения.

Если стек пуст, вы не можете постить его, если вы получите ошибку стека стека.

Если стек заполнен, вы не можете нажать, если вы получите ошибку.

Таким образом, переполнение стека появляется там, где вы слишком много выделяете в стек. Например, в упомянутой рекурсии.

Некоторые реализации оптимизируют некоторые формы рекурсий. Рекурсия хвоста в частности. Рекурсивные подпрограммы хвоста — это форма подпрограмм, в которых рекурсивный вызов появляется как последняя вещь, что делает процедура. Такой обычный вызов просто сводится к прыжку.

Некоторые реализации до сих пор реализуют свои собственные стеки для рекурсии, поэтому они позволяют рекурсии продолжать до тех пор, пока система не исчерпает память.

Самая простая вещь, которую вы могли бы попробовать, это увеличить размер стека, если сможете. Если вы не можете этого сделать, вторая лучшая вещь — посмотреть, есть ли что-то, что явно вызывает переполнение стека. Попробуйте, напечатав что-то до и после вызова в рутину. Это поможет вам узнать провалившуюся процедуру.

8 Greg [2008-10-18 11:19:00]

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

Как вы говорите, вам нужно показать код.: -)

Ошибка обычно происходит, когда ваша функция вызывает слишком много гнезд. См. Поток Qaru Code Golf для некоторых примеров того, как это происходит (хотя в случае этого вопроса ответы намеренно вызывают переполнение стека).

5 Vikram [2012-07-18 00:23:00]

StackOverflowError относится к стеку, поскольку OutOfMemoryError относится к куче.

Неограниченные рекурсивные вызовы приводят к тому, что пространство стека израсходовано.

В следующем примере StackOverflowError :

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

4 splattne [2008-10-18 11:43:00]

Наиболее распространенной причиной является чрезмерно глубокая или бесконечная рекурсия. Если это ваша проблема, этот учебник о Java Recursion может помочь понять проблему.

3 Yiling [2013-01-16 22:49:00]

Вот пример рекурсивного алгоритма для обращения к односвязному списку. На ноутбуке со следующей спецификацией (память 4G, процессор Intel Core i5 2.3GHz, 64-разрядная версия Windows 7) эта функция будет запущена с ошибкой StackOverflow для связанного списка размером около 10 000.

Я хочу сказать, что мы должны использовать рекурсию разумно, всегда принимая во внимание масштаб системы. Часто рекурсия может быть преобразована в итеративную программу, которая масштабируется лучше. (Одна итеративная версия того же алгоритма приведена в нижней части страницы, она меняет одноуровневый список размером 1 миллион за 9 миллисекунд.)

Итеративная версия того же алгоритма:

3 Rahul Sah [2017-03-30 14:08:00]

StackOverflowError — это ошибка времени выполнения в java.

Он вызывается, когда объем памяти стека вызовов распределяется с помощью JVM.

Обычный случай aa StackOverflowError который бросается, когда стек вызовов превышает из-за чрезмерной глубокой или бесконечной рекурсии.

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

0 Sergiu [2014-01-25 17:01:00]

Термин «переполнение стека» (переполнение) часто используется, но является неправильным; атаки не переполняют стек, а буферы в стеке.

0 John S. [2016-02-12 02:02:00]

A StackOverflowError в основном заключается в том, что вы пытаетесь сделать что-то, что, скорее всего, называет себя и продолжается бесконечно (или пока оно не даст StackOverflowError).

add5(a) будет вызывать себя, а затем снова называть себя и т.д.

Java.lang.StackOverflowError при сохранении объекта jpa

Я создаю приложение, используя JPA, JSF, EJB, Derby. На данный момент приложение все еще мало. У меня есть форма в приложении для добавления новых продуктов. При добавлении данных в db он идет гладко, пока я не перезапущу приложение или сервер. Когда я перезапускаю сервер или приложение, я получаю java.lang.StackOverflowError , я все еще могу запросить db для данных, представленных продуктом db, но создание продукта невозможно. На данный момент у меня всего 5 записей в db, но я очень обеспокоен этим случаем так рано.

Это Ejb (Getter, setter и конструкторы удалены для простоты):

это ProductController (Getter, setter и конструкторы удалены для простоты):

Категория объекта (Getters, seters, hash() и конструкторы удалены для простоты):

Product Entity (Getters, seters, hash() и конструкторы удалены для простоты):

В вашем классе Category есть список Products и в методе equals класса Category , который вы выполняете

который вызывает метод equals в классе Product , метод equals в классе Product , который выполняет

который снова вызывает метод equals на Category , и весь процесс повторяется, вызывая переполнение стека.

  • Либо удалить двунаправленную зависимость.
  • Исправить метод equals.

Надеюсь, что это поможет.

Я подозреваю, что круговая зависимость является основной причиной проблемы. Я думаю, что вы сопоставили Product либо в Category , либо SaleDetails , либо оба объекта. Если это так, он вызовет проблему с круглым ссылочным номером при сериализации объекта Product , в результате чего произойдет ошибка StackOverFlow .

Я думаю, у вас есть два варианта:

  • Удалите отображение bi-dreictional , если его можно избежать.
  • Внесите readObject() и writeObject() методы в классы Product , Category и SaleDetails и избегайте чтения/записи объектов в кругах.
Читать еще:  Отладка javascript в google chrome

Надеюсь, что это поможет.

Как сказал @Sajan. У вас есть циклическая зависимость в ваших equals().
Вам нужно изменить метод equals() в вашем классе категории, чтобы не ссылаться на список «Product» и «categoryId». Вероятно, он должен быть следующим:

В методе equals() в классе Product вам может потребоваться удалить «ProductId» и «Price».

Методы equals() и hashcode() важны, поскольку они определяют равенство объектов, когда вы используете объекты в отдельном состоянии и добавляете их в java.util.Set.
Рекомендуемая реализация заключается в использовании «свойств, которые образуют идентификатор естественного ключа» в реализациях equals() и hashcode().

Категория —
Скажем, у вас есть CategoryA с ProductA и ProductB. Не исключено, что те же ProductA и ProductB будут привязаны к другой категории, называемой CategoryB. Таким образом, они не должны быть частью реализации equals().

Продукт —
Включить «Категория» в Product.equals() зависит от того, является ли «Категория» частью идентификатора естественного ключа для Продукта. И тот факт, что вы используете список внутри категории, означает, что вы не слишком обеспокоены равенством объектов. Если вас беспокоит равенство(), я бы рекомендовал вам изменить его на Set.

Если у вас был следующий сценарий —

name — Камера | цена — $100 | категория — Электроника

name — HandyCam | Цена — $200 | Категория — Электроника

Если категория «Электроника» имеет набор из двух продуктов, как показано выше.
Если у вас был следующий пример кода —

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

В приведенном выше сценарии вам нужно иметь «Категория» и «имя» в методе equals() «Product».

Кажется, что проблема в классе Category — equals делает что-то, что в свою очередь вызывает equals, создавая бесконечный цикл

Что такое StackOverflowError?

что это StackOverflowError , что вызывает это, и как я должен с ними бороться?

13 ответов

параметры и локальные переменные выделяется на стек (со ссылочными типами объект живет на кучу и переменная ссылается на этот объект). Стек обычно живет в верхний конец вашего адресного пространства и по мере его использования он направляется к дно адресного пространства (т. е. к нулю).

ваш процесс также имеет кучу, который живет в дно конце процесс. По мере выделения памяти эта куча может расти к верхнему концу адресного пространства. Как вы можете видеть, существует потенциал для кучи «наехать» со стеком (немного похоже на тектонические плиты. ).

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

однако, с GUI программирование, можно генерировать косвенная рекурсия. Например, приложение может обрабатывать сообщения paint и при их обработке вызывать функцию, которая заставляет систему отправлять другое сообщение paint. Здесь вы явно не вызывали себя, но OS / VM сделала это за вас.

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

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

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

локальные переменные хранятся в стек:

если вы посмотрите на изображение, вы должны быть в состоянии понять, как вещи работают.

когда вызов функции вызывается приложением Java, в стеке вызовов выделяется кадр стека. Фрейм стека содержит параметры вызываемого метода, его локальный параметры и обратный адрес метода. Обратный адрес обозначает точку выполнения, с которой выполнение программы должно продолжаться после возвращения вызванного метода. Если нет места для нового кадра стека, то StackOverflowError выбрасывается виртуальной машиной Java (JVM).

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

пример кидания StackOverflowError показано ниже:

StackOverflowErrorExample.java:

в этом примере мы определяем рекурсивный метод, называемый recursivePrint который печатает целое число, а затем вызывает себя со следующим последовательным целым числом в качестве аргумента. Рекурсия заканчивается, пока мы не перейдем в 0 в качестве параметра. Однако, в нашем примере, мы передали параметр из 1 и его возрастающих последователей, следовательно, рекурсия никогда не закончится.

пример выполнения, используя -Xss1M флаг, указывающий размер стека потоков, равный 1 МБ, показан ниже:

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

как бороться с StackOverflowError

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

если вы убедились, что рекурсия реализовано правильно, вы можете увеличить размер стека, в чтобы разрешить большее количество вызовов. В зависимости от Java Установлена виртуальная машина (JVM), размер стека потоков по умолчанию может равным 512КБ или 1МБ. Можно увеличить стек потоков размер с помощью -Xss флаг. Этот флаг можно указать либо через конфигурация проекта, или через командную строку. Формат

Если у вас есть функция как:

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

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

Если стек пуст, вы не можете поп, если вы это сделаете, вы получите ошибку стека underflow.

Если стек заполнен, вы не можете нажать, если вы это сделаете вы получите ошибку переполнения стека.

таким образом, переполнение стека появляется там, где вы выделяете слишком много в стек. Например, в упомянутой рекурсии.

некоторые реализации оптимизируют некоторые формы рекурсий. В частности, хвостовая рекурсия. Хвост рекурсивные подпрограммы-это форма подпрограмм, где рекурсивный вызов появляется как последнее, что делает подпрограмма. Такой рутинный вызов просто сводится к прыжку.

некоторые реализации заходят так далеко, что реализуют их собственные стеки для рекурсии, поэтому они позволяют рекурсии продолжаться до тех пор, пока в системе не закончится память.

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

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

Читать еще:  Java lang illegalstateexception что это

Как вы говорите, вам нужно показать какой-то код. 🙂

ошибка переполнения стека обычно происходит, когда ваша функция вызывает nest слишком глубоко. Вижу Код Переполнения Стека Golf поток для некоторых примеров того, как это происходит (хотя в случае этого вопроса ответы намеренно вызывают переполнение стека).

наиболее распространенной причиной переполнения стека является чрезмерно глубокая или бесконечная рекурсия. Если это ваша проблема, этот учебник о рекурсии Java может помочь понять проблему.

StackOverflowError находится в стеке, как OutOfMemoryError находится в куче.

неограниченные рекурсивные вызовы приводят к использованию пространства стека.

следующий пример производит StackOverflowError :

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

вот пример рекурсивного алгоритма для реверсирования односвязного списка. На ноутбуке со следующей спецификацией (память 4G, процессор Intel Core i5 2.3 GHz, 64 бит Windows 7) эта функция будет работать с ошибкой StackOverflow для связанного списка размера, близкого к 10,000.

Я считаю, что мы должны использовать рекурсию разумно, всегда принимая во внимание масштаб системы. Часто рекурсию можно преобразовать в итеративную программу, которая лучше масштабируется. (Одна итерация версия того же алгоритма приведена в нижней части страницы, она отменяет односвязный список размером 1 миллион за 9 миллисекунд.)

итеративная версия того же алгоритма:

A StackOverflowError является ошибкой времени выполнения в java.

он выбрасывается при превышении объема памяти стека вызовов, выделенного JVM.

обычный случай a StackOverflowError выбрасывается, когда стек вызовов превышает из-за чрезмерной глубокой или бесконечной рекурсии.

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

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

add5(a) вызовет себя, а затем вызовет себя снова и так далее.

термин» переполнение стека (переполнение) » часто используется, но неправильно; атаки не переполняют стек, а буферы в стеке.

StackOverflowError в Java

1. Обзор

StackOverflowError может раздражать разработчиков Java, поскольку это одна из самых распространенных ошибок времени исполнения, с которыми мы можем столкнуться.

В этой статье мы увидим, как может возникнуть эта ошибка, рассмотрев множество примеров кода, а также то, как мы можем с ней справиться

2. Фреймы стека и как происходит StackOverflowError

Давайте начнем с основ. Когда вызывается метод, в стеке вызовов создается новый кадр стека. Этот кадр стека содержит параметры вызванного метода, его локальные переменные и адрес возврата метода, т.е. точку, с которой выполнение метода должно продолжаться после вызванный метод вернулся.

Создание кадров стека будет продолжаться до тех пор, пока не будет достигнут конец вызовов методов, найденных во вложенных методах.

Во время этого процесса, если JVM столкнется с ситуацией, когда нет места для создания нового стекового фрейма, он выдаст StackOverflowError . **

Наиболее распространенной причиной, по которой JVM сталкивается с этой ситуацией, является неопределенная/бесконечная рекурсия — в описании Javadoc для StackOverflowError упоминается, что ошибка возникает в результате слишком глубокой рекурсии в конкретном фрагменте кода.

Однако рекурсия не является единственной причиной этой ошибки. Это также может произойти в ситуации, когда приложение продолжает вызывать методы изнутри методов, пока стек не будет исчерпан . Это редкий случай, так как ни один разработчик не будет намеренно следовать плохой практике кодирования. Другой редкой причиной является наличие большого числа локальных переменных внутри метода .

StackOverflowError также может быть выброшено, когда приложение разработано так, чтобы иметь циклические отношения между классами . В этой ситуации конструкторы друг друга вызывают неоднократно, что приводит к возникновению этой ошибки. Это также можно рассматривать как форму рекурсии.

Другой интересный сценарий, который вызывает эту ошибку, заключается в том, что экземпляр класса создается в том же классе, что и переменная экземпляра этого класса . Это приведет к тому, что конструктор одного и того же класса будет вызываться снова и снова (рекурсивно), что в итоге приводит к StackOverflowError.

В следующем разделе мы рассмотрим некоторые примеры кода, демонстрирующие эти сценарии.

3. StackOverflowError в действии

В приведенном ниже примере StackOverflowError будет сгенерировано из-за непреднамеренной рекурсии, когда разработчик забыл указать условие завершения для рекурсивного поведения:

Здесь ошибка генерируется во всех случаях для любого значения, переданного в метод:

Однако в следующем примере условие завершения указывается, но никогда не выполняется, если значение -1 передается методу calculateFactorial () , что вызывает неопределенную/бесконечную рекурсию:

Этот набор тестов демонстрирует этот сценарий:

В этом конкретном случае ошибки можно было бы полностью избежать, если бы условие завершения было просто сформулировано как:

Вот тест, который показывает этот сценарий на практике:

Теперь давайте рассмотрим сценарий, в котором StackOverflowError происходит в результате циклических отношений между классами. Давайте рассмотрим ClassOne и ClassTwo , которые создают друг друга внутри своих конструкторов, вызывая циклические отношения:

Теперь допустим, что мы пытаемся создать экземпляр ClassOne , как показано в этом тесте:

Это заканчивается StackOverflowError , поскольку конструктор ClassOne создает экземпляр ClassTwo, , а конструктор ClassTwo снова создает экземпляр ClassOne. И это повторяется до тех пор, пока не переполнится стек.

Далее мы рассмотрим, что происходит, когда создается экземпляр класса в том же классе, что и переменная экземпляра этого класса.

Как видно из следующего примера, AccountHolder создает себя как переменная экземпляра jointAccountHolder :

Когда создается экземпляр класса AccountHolder _, , StackOverflowError_ генерируется из-за рекурсивного вызова конструктора, как видно из этого теста:

4. Работа с StackOverflowError

Лучшее, что можно сделать при обнаружении StackOverflowError , — это внимательно осмотреть трассировку стека, чтобы идентифицировать повторяющийся шаблон номеров строк. Это позволит нам найти код с проблемной рекурсией.

Давайте рассмотрим несколько трассировок стека, вызванных примерами кода, которые мы видели ранее.

Эта трассировка стека создается InfiniteRecursionWithTerminationConditionManualTest , если мы пропускаем объявление expected исключения:

Здесь строка № 5 видна повторяющейся. Это где рекурсивный вызов делается. Теперь нужно просто изучить код, чтобы убедиться, что рекурсия выполнена правильно.

Вот трассировка стека, которую мы получаем, выполняя CyclicDependancyManualTest (опять же, без expected исключения):

Эта трассировка стека показывает номера строк, которые вызывают проблему в двух классах, которые находятся в циклическом отношении. Строка № 9 ClassTwo и строка № 9 ClassOne указывают на местоположение внутри конструктора, в котором он пытается создать экземпляр другого класса.

Как только код тщательно проверен, и если ни одно из следующего (или любая другая логическая ошибка кода) не является причиной ошибки:

Неправильно реализованная рекурсия (т.е. без условия завершения)

Циклическая зависимость между классами

Создание класса в том же классе, что и переменная экземпляра

Было бы неплохо попытаться увеличить размер стека. В зависимости от установленной JVM размер стека по умолчанию может варьироваться.

Флаг -Xss может использоваться для увеличения размера стека, либо из конфигурации проекта, либо из командной строки.

5. Заключение

В этой статье мы более подробно рассмотрели StackOverflowError , в том числе о том, как код Java может вызвать его, и как мы можем его диагностировать и исправлять.

Исходный код, связанный с этой статьей, можно найти на over на GitHub .

Ссылка на основную публикацию
Adblock
detector