В чём разница между StackOverFlowError и OutOfMemoryError?
StackOverFlowError и OutOfMemoryError выбрасываются при недостатке памяти у jvm , но в чём их разница?
Отслеживать
4,493 5 5 золотых знаков 12 12 серебряных знаков 37 37 бронзовых знаков
задан 2 мар 2023 в 11:39
2,326 2 2 золотых знака 11 11 серебряных знаков 38 38 бронзовых знаков
ассоциация:stackoverflow.com/questions/11435613/…
4 сен 2023 в 14:53
1 ответ 1
Сортировка: Сброс на вариант по умолчанию
StackOverFlowError
Эта ошибка происходит, когда переполнен стек (по-английски — stack). Если кратко, то в стеке хранятся только ссылки на объекты, которые находятся в куче. Ещё в стеке хранятся примитивы и список методов, «кто кого вызвал». Вообщем StackOverFlowError происходит при зацикливании вызова методов. Например:
public static void doSomething()
При вызове метода doSomething() произойдёт StackOverFlowError , так как стек будет переполнен.
Чтобы исправить StackOverFlowError нужно найти такое зацикливание и убрать его. На самом деле, вызов метода из самого себя использовать можно, но делать это стоит осторожно. Это будет называться рекурсия. Вот пример использования рекурсии для вычисления факториала:
public static int factorial(int number) < if(number < 0)< throw new IllegalArgumentException("Число меньше 0"); >if(number == 0 || number == 1) < return 1; >return factorial(number - 1) * number; >
В таком случае StackOverFlowError не произойдёт, так как из метода в конце-концов будет выполнен выход.
В рекурсивных методах следует также проверять, что число, пришедшее в параметрах, не слишком большое. В противном случае может оказаться, что метод будет вызывать сам себя хоть и не бесконечное, но всё же достаточное количество раз, чтобы произошла ошибка StackOverFlowError .
OutOfMemoryError
Это ошибка происходит, когда переполнена куча (по-английски — heap). Если кратко, то в куче хранятся объекты. Именно объекты, а не ссылки на них. То есть OutOfMemoryError произойдёт тогда, когда в куче кончится место создавать объекты. Вот пример:
public class Test < public static void main(String [] args)< Object[][] toManyObjects = new Object[1000000][1000000]; >>
Этот код бует скомпилирован, но при запуске произойдёт OutOfMemoryError :
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at Test.main(Test.java:5)
Ведь в массиве гигантское количество объектов, и в куче нет столько места.
Конечно, никто не будет делать такие глупые ошибки. Обычно OutOfMemoryError происходит тоже при зацикливании, но не при бесконечном вызове метода, а в обычных циклах ( for , while , do-while ), если из таких циклов нет выхода, а в теле цикла создаются какие-либо объекты.
Примеров кодов с OutOfMemoryError масса в интернете: можете посмотреть вопросы на StackOverFlow на русском , в которых упоминается OutOfMemoryError , если вам интересно посмотреть в каких случаях такая ошибка может произойти.
Подведу итоги
- OutOfMemoryError происходит при переполнении кучи, а StackOverFlowError — при переполнении стека;
- StackOverFlowError происходит при бесконечном вызове метода, а OutOfMemoryError — при зацикливании создания объектов;
- И того, и другое нужно исправить: найти зацикливание и устранить его.
Что такое StackOverflow ошибка: раскрываем тайну
![]()
В мире программистов ошибка «stack overflow» очень известн а б лагодаря тому, что этот вид ошибки довольно распространен. А сам термин «stack overflow» известен еще больше , чем ошибка, благодаря одноименному англоязычному ресурсу «StackOverflow». Это сообщество программистов международного масштаба , и еще куча всего интересного. Поэтому не нужно путать ошибку « stack overflow » и веб-ресурс с таким же названием. В нашей статье речь пойдет об ошибке.
Ошибка «stack overflow» связана с переполнением стека. Она появляется в том случае, когда в стеке должно сохранит ь ся больше информации, чем он может уместить. Объем памяти стека задает программист при запуске программы. Если в процессе выполнения программы стек переполняется, тогда возникает ошибка « stack overflow » и программа аварийно завершает работу. Причин возникновения подобной ошибки может быть несколько.
Ошибка « stack overflow »
- бесконечная рекурсия;
- глубокая рекурсия;
- проблемы с переменными в стеке.
Бесконечная рекурсия и ошибка «stack overflow»
- забывает прописывать условие для выхода из рекурсии;
- пишет неосознанную косвенную рекурсию.
Глубокая рекурсия и ошибка «stack overflow»
- отыскать другой программный алгоритм для решения поставленной задачи, чтобы избежать применени я рекурсии;
- «вынести» рекурсию за пределы аппаратного стека в динамический;
- и другое.
Проблемы с переменными в стеке и ошибка «stack overflow»
Если взглянуть на популярность возникновения «stack overflow error», то причина с проблемными переменными в стеке стоит на первом месте. Кроется она в том, что программист изначально выделяет слишком много памяти локальной переменной.
Например:
int function()
double b[1000000]
>
В данном случае может возникнуть такая ситуация, что массиву потребуется объем памяти, который стек не способен будет обеспечить, а значит , возникнет ошибка «stack overflow».
Заключение
Ошибка « stack overflow » возникает довольно часто. Каждый конкретный случай ее возникновения требует собственного решения. Одна причина объединяет возникновение такой ошибки — невнимательность программиста. Если « stack overflow error » возникла, значит , программист где-то что-то упустил или не доглядел.
Мы будем очень благодарны
если под понравившемся материалом Вы нажмёте одну из кнопок социальных сетей и поделитесь с друзьями.
StackOverflowError в Java
StackOverflowError может раздражать разработчиков Java, так как это одна из самых распространенных ошибок времени выполнения, с которыми мы можем столкнуться.
В этой статье мы увидим, как эта ошибка может возникнуть, рассмотрев различные примеры кода, а также как мы можем с ней справиться.
2. Кадры стека и как возникает StackOverflowError
Начнем с основ. При вызове метода в стеке вызовов создается новый кадр стека . Этот кадр стека содержит параметры вызываемого метода, его локальные переменные и адрес возврата метода, т. е. точку, с которой выполнение метода должно продолжаться после возврата вызванного метода.
Создание кадров стека будет продолжаться до тех пор, пока не будет достигнут конец вызовов методов внутри вложенных методов.
Во время этого процесса, если JVM сталкивается с ситуацией, когда нет места для создания нового кадра стека, он выдает StackOverflowError .
Наиболее распространенной причиной, по которой JVM сталкивается с этой ситуацией, является непрерывная/бесконечная рекурсия — в описании Javadoc для StackOverflowError упоминается, что ошибка возникает в результате слишком глубокой рекурсии в конкретном фрагменте кода.
Однако рекурсия — не единственная причина этой ошибки. Это также может произойти в ситуации, когда приложение продолжает вызывать методы из методов до тех пор, пока стек не будет исчерпан . Это редкий случай, поскольку ни один разработчик не стал бы намеренно следовать плохим методам написания кода. Другой редкой причиной является наличие большого количества локальных переменных внутри метода .
Ошибка StackOverflowError также может быть вызвана, когда приложение спроектировано так, чтобы иметь циклические отношения между классами . В этой ситуации конструкторы друг друга вызываются повторно, что приводит к возникновению этой ошибки. Это также можно рассматривать как форму рекурсии.
Еще один интересный сценарий, вызывающий эту ошибку, заключается в том , что экземпляр класса создается в том же классе, что и переменная экземпляра этого класса . Это приведет к тому, что конструктор одного и того же класса будет вызываться снова и снова (рекурсивно), что в конечном итоге приведет к ошибке StackOverflowError.
В следующем разделе мы рассмотрим некоторые примеры кода, демонстрирующие эти сценарии.
3. StackOverflowError в действии
В приведенном ниже примере StackOverflowError будет выдан из-за непреднамеренной рекурсии, когда разработчик забыл указать условие завершения рекурсивного поведения:
public class UnintendedInfiniteRecursion public int calculateFactorial(int number) return number * calculateFactorial(number - 1); > >
Здесь ошибка возникает во всех случаях для любого значения, переданного в метод:
public class UnintendedInfiniteRecursionManualTest @Test(expected = StackOverflowError.class) public void givenPositiveIntNoOne_whenCalFact_thenThrowsException() int numToCalcFactorial= 1; UnintendedInfiniteRecursion uir = new UnintendedInfiniteRecursion(); uir.calculateFactorial(numToCalcFactorial); > @Test(expected = StackOverflowError.class) public void givenPositiveIntGtOne_whenCalcFact_thenThrowsException() int numToCalcFactorial= 2; UnintendedInfiniteRecursion uir = new UnintendedInfiniteRecursion(); uir.calculateFactorial(numToCalcFactorial); > @Test(expected = StackOverflowError.class) public void givenNegativeInt_whenCalcFact_thenThrowsException() int numToCalcFactorial= -1; UnintendedInfiniteRecursion uir = new UnintendedInfiniteRecursion(); uir.calculateFactorial(numToCalcFactorial); > >
Однако в следующем примере указано условие завершения, но оно никогда не выполняется, если методу calculateFactorial() передается значение -1 , что вызывает незавершенную/бесконечную рекурсию: «
public class InfiniteRecursionWithTerminationCondition public int calculateFactorial(int number) return number == 1 ? 1 : number * calculateFactorial(number - 1); > >
Этот набор тестов демонстрирует этот сценарий:
public class InfiniteRecursionWithTerminationConditionManualTest @Test public void givenPositiveIntNoOne_whenCalcFact_thenCorrectlyCalc() int numToCalcFactorial = 1; InfiniteRecursionWithTerminationCondition irtc = new InfiniteRecursionWithTerminationCondition(); assertEquals(1, irtc.calculateFactorial(numToCalcFactorial)); > @Test public void givenPositiveIntGtOne_whenCalcFact_thenCorrectlyCalc() int numToCalcFactorial = 5; InfiniteRecursionWithTerminationCondition irtc = new InfiniteRecursionWithTerminationCondition(); assertEquals(120, irtc.calculateFactorial(numToCalcFactorial)); > @Test(expected = StackOverflowError.class) public void givenNegativeInt_whenCalcFact_thenThrowsException() int numToCalcFactorial = -1; InfiniteRecursionWithTerminationCondition irtc = new InfiniteRecursionWithTerminationCondition(); irtc.calculateFactorial(numToCalcFactorial); > >
В этом конкретном случае ошибки можно было бы полностью избежать, если бы условие завершения было сформулировано просто так:
public class RecursionWithCorrectTerminationCondition public int calculateFactorial(int number) return number 1 ? 1 : number * calculateFactorial(number - 1); > >
Вот тест, который показывает этот сценарий на практике:
public class RecursionWithCorrectTerminationConditionManualTest @Test public void givenNegativeInt_whenCalcFact_thenCorrectlyCalc() int numToCalcFactorial = -1; RecursionWithCorrectTerminationCondition rctc = new RecursionWithCorrectTerminationCondition(); assertEquals(1, rctc.calculateFactorial(numToCalcFactorial)); > >
Теперь давайте рассмотрим сценарий, в котором StackOverflowError возникает в результате циклических отношений между классами. Давайте рассмотрим ClassOne и ClassTwo , которые создают экземпляры друг друга внутри своих конструкторов, вызывая циклическую связь:
public class ClassOne private int oneValue; private ClassTwo clsTwoInstance = null; public ClassOne() oneValue = 0; clsTwoInstance = new ClassTwo(); > public ClassOne(int oneValue, ClassTwo clsTwoInstance) this.oneValue = oneValue; this.clsTwoInstance = clsTwoInstance; > >
public class ClassTwo private int twoValue; private ClassOne clsOneInstance = null; public ClassTwo() twoValue = 10; clsOneInstance = new ClassOne(); > public ClassTwo(int twoValue, ClassOne clsOneInstance) this.twoValue = twoValue; this.clsOneInstance = clsOneInstance; > >
Теперь предположим, что мы пытаемся создать экземпляр ClassOne , как показано в этом тесте:
public class CyclicDependancyManualTest @Test(expected = StackOverflowError.class) public void whenInstanciatingClassOne_thenThrowsException() ClassOne obj = new ClassOne(); > >
Это заканчивается StackOverflowError , поскольку конструктор ClassOne создает экземпляр ClassTwo, а конструктор ClassTwo снова создает экземпляр ClassOne. И это повторяется неоднократно, пока не переполнится стек.
Далее мы рассмотрим, что происходит, когда экземпляр класса создается в том же классе, что и переменная экземпляра этого класса.
Как видно из следующего примера, AccountHolder инстанцирует себя как переменную экземпляраjoinAccountHolder :
public class AccountHolder private String firstName; private String lastName; AccountHolder jointAccountHolder = new AccountHolder(); >
Когда создается экземпляр класса AccountHolder , выдается StackOverflowError из-за рекурсивного вызова конструктора, как показано в этом тесте:
public class AccountHolderManualTest @Test(expected = StackOverflowError.class) public void whenInstanciatingAccountHolder_thenThrowsException() AccountHolder holder = new AccountHolder(); > >
4. Работа с StackOverflowError
При обнаружении ошибки StackOverflowError лучше всего внимательно изучить трассировку стека, чтобы определить повторяющийся шаблон номеров строк. Это позволит нам найти код с проблемной рекурсией.
Давайте рассмотрим несколько трассировок стека, вызванных примерами кода, которые мы видели ранее.
Эта трассировка стека создается InfiniteRecursionWithTerminationConditionManualTest , если мы опускаем ожидаемое объявление исключения:
java.lang.StackOverflowError at c.b.s.InfiniteRecursionWithTerminationCondition .calculateFactorial(InfiniteRecursionWithTerminationCondition.java:5) at c.b.s.InfiniteRecursionWithTerminationCondition .calculateFactorial(InfiniteRecursionWithTerminationCondition.java:5) at c.b.s.InfiniteRecursionWithTerminationCondition .calculateFactorial(InfiniteRecursionWithTerminationCondition.java:5) at c.b.s.InfiniteRecursionWithTerminationCondition .calculateFactorial(InfiniteRecursionWithTerminationCondition.java:5)
Здесь можно увидеть повторяющуюся строку номер 5. Здесь выполняется рекурсивный вызов. Теперь осталось только изучить код, чтобы убедиться, что рекурсия выполнена правильно.
Вот трассировка стека, которую мы получаем, выполняя CyclicDependancyManualTest (опять же, без ожидаемого исключения):
java.lang.StackOverflowError at c.b.s.ClassTwo.init>(ClassTwo.java:9) at c.b.s.ClassOne.init>(ClassOne.java:9) at c.b.s.ClassTwo.init>(ClassTwo.java:9) at c.b.s.ClassOne.init>(ClassOne.java:9)
Эта трассировка стека показывает номера строк, которые вызывают проблему в двух классах, находящихся в циклической связи. Строка номер 9 ClassTwo и строка номер 9 ClassOne указывают на место внутри конструктора, где он пытается создать экземпляр другого класса.
После тщательной проверки кода и если ни одно из следующих (или любая другая логическая ошибка кода) не является причиной ошибки:
- Неправильно реализованная рекурсия (т.е. без условия завершения)
- Циклическая зависимость между классами
- Создание экземпляра класса в том же классе, что и переменная экземпляра этого класса
Было бы неплохо попробовать увеличить размер стека. В зависимости от установленной JVM размер стека по умолчанию может различаться.
Флаг -Xss можно использовать для увеличения размера стека либо из конфигурации проекта, либо из командной строки.
5. Вывод
В этой статье мы более подробно рассмотрели StackOverflowError , в том числе то, как код Java может вызвать ее, и как мы можем ее диагностировать и исправить.
Исходный код, относящийся к этой статье, можно найти на GitHub .
Что на самом деле вызывает ошибку StackOverflow в Java?
StackOverflowError просто сигнализирует о том, что памяти больше нет. Он расширяет класс VirtualMachineError, что указывает на то, что JVM (виртуальная машина Java) повреждена или у нее закончились ресурсы и она не может работать.
Если у вас есть такая функция, как:
int myFunction() < // ваш код myFunction(); >
В приведенном выше коде myFunction() будет продолжать называть себя, все глубже и глубже, и когда пространство, используемое для отслеживания того, какие функции вы находитесь, заполнено, вы получаете ошибку stackoverflow. Общей причиной переполнения стека является плохой рекурсивный вызов. Как правило, это вызвано, когда ваши рекурсивные функции не имеют правильного условия завершения, поэтому он заканчивается тем, что навсегда называет себя.
Причина использования StackOverflowError
Параметры и локальные переменные выделяются в стеке. Стек обычно находится в верхнем конце вашего адресного пространства, и, поскольку он используется, он направляется к нижней части адресного пространства. У вашего процесса также есть куча, которая живет в нижней части вашего процесса. Когда вы выделяете память, эта куча может расти в верхнем конце вашего адресного пространства. Как вы можете видеть, существует вероятность того, что куча «столкнется» со стеклом. Если нет места для нового стека кадров, StackOverflowError вызывается виртуальной машиной Java (JVM).
Если стек заполнен, вы не можете нажать, если вы это сделаете, вы получите ошибку переполнения стека.
Если стек пуст, вы не можете поп, если вы это сделаете, вы получите ошибку стека стека.
Что такое stacktrace?
Столбец — очень полезный инструмент для отладки. Это список вызовов метода, в которых приложение было посередине, когда было выбрано исключение. Это очень полезно, потому что оно не только показывает вам, где произошла ошибка, но также и то, как программа оказалась в этом месте кода.
Столбец — очень полезный инструмент для отладки. Это список вызовов метода, в которых приложение было посередине, когда было выбрано исключение. Это очень полезно, потому что оно не только показывает вам, где произошла ошибка, но также и то, как программа оказалась в этом месте кода.