Чтобы описать это, сначала давайте разберемся, как хранятся локальные переменные и объекты.
Локальные переменные хранятся в стеке :
Если вы посмотрели на изображение, вы должны понимать, как все работает.
Когда вызов функции вызывается приложением Java, в стеке вызовов выделяется кадр стека. Кадр стека содержит параметры вызванного метода, его локальные параметры и адрес возврата метода. Адрес возврата обозначает точку выполнения, с которой выполнение программы должно продолжаться после возврата вызванного метода. Если нет места для нового стекового фрейма, StackOverflowError
он генерируется виртуальной машиной Java (JVM).
Наиболее распространенным случаем, который может исчерпать стек приложения Java, является рекурсия. В рекурсии метод вызывает себя во время выполнения. Рекурсия считается мощной техникой программирования общего назначения, но ее следует избегать с осторожностью StackOverflowError
.
Пример броска a StackOverflowError
показан ниже:
StackOverflowErrorExample.java:
public class StackOverflowErrorExample {
public static void recursivePrint(int num) {
System.out.println("Number: " + num);
if (num == 0)
return;
else
recursivePrint(++num);
}
public static void main(String[] args) {
StackOverflowErrorExample.recursivePrint(1);
}
}
В этом примере мы определяем рекурсивный метод, recursivePrint
который вызывается, который печатает целое число, а затем вызывает сам себя со следующим последующим целым числом в качестве аргумента. Рекурсия заканчивается, пока мы не передадим в 0
качестве параметра. Однако в нашем примере мы передали параметр от 1 и его растущих последователей, следовательно, рекурсия никогда не завершится.
Пример выполнения с использованием -Xss1M
флага, который задает размер стека потока равным 1 МБ, показан ниже:
Number: 1
Number: 2
Number: 3
...
Number: 6262
Number: 6263
Number: 6264
Number: 6265
Number: 6266
Exception in thread "main" java.lang.StackOverflowError
at java.io.PrintStream.write(PrintStream.java:480)
at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:221)
at sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:291)
at sun.nio.cs.StreamEncoder.flushBuffer(StreamEncoder.java:104)
at java.io.OutputStreamWriter.flushBuffer(OutputStreamWriter.java:185)
at java.io.PrintStream.write(PrintStream.java:527)
at java.io.PrintStream.print(PrintStream.java:669)
at java.io.PrintStream.println(PrintStream.java:806)
at StackOverflowErrorExample.recursivePrint(StackOverflowErrorExample.java:4)
at StackOverflowErrorExample.recursivePrint(StackOverflowErrorExample.java:9)
at StackOverflowErrorExample.recursivePrint(StackOverflowErrorExample.java:9)
at StackOverflowErrorExample.recursivePrint(StackOverflowErrorExample.java:9)
...
В зависимости от начальной конфигурации JVM, результаты могут отличаться, но в конечном итоге StackOverflowError
должны быть выброшены. Этот пример является очень хорошим примером того, как рекурсия может вызвать проблемы, если не применять ее с осторожностью.
Как бороться с StackOverflowError
Самое простое решение - тщательно осмотреть трассировку стека и обнаружить повторяющуюся последовательность номеров строк. Эти номера строк указывают на рекурсивно вызываемый код. Как только вы обнаружите эти строки, вы должны тщательно проверить свой код и понять, почему рекурсия никогда не заканчивается.
Если вы убедились, что рекурсия реализована правильно, вы можете увеличить размер стека, чтобы разрешить большее количество вызовов. В зависимости от установленной виртуальной машины Java (JVM) размер стека потока по умолчанию может быть равен 512 КБ или 1 МБ . Вы можете увеличить размер стека потока, используя -Xss
флаг. Этот флаг можно указать либо через конфигурацию проекта, либо через командную строку. Формат
-Xss
аргумента:
-Xss<size>[g|G|m|M|k|K]