To describe this, first let us understand how local variables and objects are stored.
Local variable are stored on the stack:
If you looked at the image you should be able to understand how things are working.
When a function call is invoked by a Java application, a stack frame is allocated on the call stack. The stack frame contains the parameters of the invoked method, its local parameters, and the return address of the method. The return address denotes the execution point from which, the program execution shall continue after the invoked method returns. If there is no space for a new stack frame then, the StackOverflowError
is thrown by the Java Virtual Machine (JVM).
The most common case that can possibly exhaust a Java application’s stack is recursion. In recursion, a method invokes itself during its execution. Recursion is considered as a powerful general-purpose programming technique, but it must be used with caution, to avoid StackOverflowError
.
An example of throwing a StackOverflowError
is shown below:
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);
}
}
In this example, we define a recursive method, called recursivePrint
that prints an integer and then, calls itself, with the next successive integer as an argument. The recursion ends until we pass in 0
as a parameter. However, in our example, we passed in the parameter from 1 and its increasing followers, consequently, the recursion will never terminate.
A sample execution, using the -Xss1M
flag that specifies the size of the thread stack to equal to 1 MB, is shown below:
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)
...
Depending on the JVM’s initial configuration, the results may differ, but eventually the StackOverflowError
shall be thrown. This example is a very good example of how recursion can cause problems, if not implemented with caution.
How to deal with the StackOverflowError
-
The simplest solution is to carefully inspect the stack trace and
detect the repeating pattern of line numbers. These line numbers
indicate the code being recursively called. Once you detect these
lines, you must carefully inspect your code and understand why the
recursion never terminates. -
If you have verified that the recursion
is implemented correctly, you can increase the stack’s size, in
order to allow a larger number of invocations. Depending on the Java
Virtual Machine (JVM) installed, the default thread stack size may
equal to either 512 KB, or 1 MB. You can increase the thread stack
size using the-Xss
flag. This flag can be specified either via the
project’s configuration, or via the command line. The format of the
-Xss
argument is:
-Xss<size>[g|G|m|M|k|K]
В мире программистов ошибка «stack overflow» очень известна благодаря тому, что этот вид ошибки довольно распространен. А сам термин «stack overflow» известен еще больше, чем ошибка, благодаря одноименному англоязычному ресурсу «StackOverflow». Это сообщество программистов международного масштаба, и еще куча всего интересного. Поэтому не нужно путать ошибку «stack overflow» и веб-ресурс с таким же названием. В нашей статье речь пойдет об ошибке.
Ошибка «stack overflow» связана с переполнением стека. Она появляется в том случае, когда в стеке должно сохраниться больше информации, чем он может уместить. Объем памяти стека задает программист при запуске программы. Если в процессе выполнения программы стек переполняется, тогда возникает ошибка «stack overflow» и программа аварийно завершает работу. Причин возникновения подобной ошибки может быть несколько.
Ошибка «stack overflow»
Нужно отметить, что ошибка «stack overflow» не связана с конкретным языком программирования, то есть она может возникнуть в программах на Java, C++, C, C# и других компилируемых языках.
Причин ее возникновения может быть несколько. К самым распространенным причинам относятся:
бесконечная рекурсия;
глубокая рекурсия;
проблемы с переменными в стеке.
Бесконечная рекурсия и ошибка «stack overflow»
Бесконечная рекурсия редко возникает самостоятельно и по неизвестным причинам. Обычно программист:
забывает прописывать условие для выхода из рекурсии;
пишет неосознанную косвенную рекурсию.
Самая частая причина из категории «бесконечной рекурсии» — программист забывает прописывать условия выхода или же прописывает, но условия выхода не срабатывают.
Вот как это выглядит на С:
int factorial (int number)
{
if (number == 0)
return 1;
return number * factorial(number — 1);
}
В описанном примере прописаны условия выхода из рекурсии, однако они никогда не сработают, если «number» будет отрицательным. Поэтому через несколько миллионов вызовов стек будет переполнен и возникнет ошибка «stack overflow». В некоторых языках программирования предусмотрена «защита» от таких рекурсий. В них рекурсия из конца функции конвертируется в цикл, что не будет расходовать стековую память. Но подобная «оптимизация» вызовет другую, менее опасную проблему — «зацикливание».
Неосознанная бесконечная рекурсия возникает в том случае, если программист по невнимательности распределяет один и тот же функционал программы между разными нагруженными функциями, а потом делает так, что они друг друга вызывают.
В коде это выглядит так:
int Object::getNumber(int index, bool& isChangeable)
{
isChangeable = true;
return getNumber(index);
}
int Object::getNumber(int index)
{
bool noValue;
return getNumber(index, noValue);
}
Глубокая рекурсия и ошибка «stack overflow»
Глубокая рекурсия — это такая рекурсия, которая имеет свое окончание через определенное время, поэтому она не бесконечная. Однако памяти стека не хватит для завершения такой рекурсии, поэтому ошибка «stack overflow» обеспечена. Обычно такая ситуация заранее просчитывается программистом,поэтому ее можно решить. Например, можно:
отыскать другой программный алгоритм для решения поставленной задачи, чтобы избежать применения рекурсии;
«вынести» рекурсию за пределы аппаратного стека в динамический;
и другое.
Глубокая рекурсия выглядит так:
void eliminateList(struct Item* that)
{
if (that == NULL)
return;
eliminateList(that->next);
free(that);
}
Проблемы с переменными в стеке и ошибка «stack overflow»
Если взглянуть на популярность возникновения «stack overflow error», то причина с проблемными переменными в стеке стоит на первом месте. Кроется она в том, что программист изначально выделяет слишком много памяти локальной переменной.
Например:
int function() {
double b[1000000]
}
В данном случае может возникнуть такая ситуация, что массиву потребуется объем памяти, который стек не способен будет обеспечить, а значит, возникнет ошибка «stack overflow».
Заключение
Ошибка «stack overflow» возникает довольно часто. Каждый конкретный случай ее возникновения требует собственного решения. Одна причина объединяет возникновение такой ошибки — невнимательность программиста. Если «stack overflow error» возникла, значит, программист где-то что-то упустил или не доглядел.
To describe this, first let us understand how local variables and objects are stored.
Local variable are stored on the stack:
If you looked at the image you should be able to understand how things are working.
When a function call is invoked by a Java application, a stack frame is allocated on the call stack. The stack frame contains the parameters of the invoked method, its local parameters, and the return address of the method. The return address denotes the execution point from which, the program execution shall continue after the invoked method returns. If there is no space for a new stack frame then, the StackOverflowError
is thrown by the Java Virtual Machine (JVM).
The most common case that can possibly exhaust a Java application’s stack is recursion. In recursion, a method invokes itself during its execution. Recursion is considered as a powerful general-purpose programming technique, but it must be used with caution, to avoid StackOverflowError
.
An example of throwing a StackOverflowError
is shown below:
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);
}
}
In this example, we define a recursive method, called recursivePrint
that prints an integer and then, calls itself, with the next successive integer as an argument. The recursion ends until we pass in 0
as a parameter. However, in our example, we passed in the parameter from 1 and its increasing followers, consequently, the recursion will never terminate.
A sample execution, using the -Xss1M
flag that specifies the size of the thread stack to equal to 1 MB, is shown below:
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)
...
Depending on the JVM’s initial configuration, the results may differ, but eventually the StackOverflowError
shall be thrown. This example is a very good example of how recursion can cause problems, if not implemented with caution.
How to deal with the StackOverflowError
-
The simplest solution is to carefully inspect the stack trace and
detect the repeating pattern of line numbers. These line numbers
indicate the code being recursively called. Once you detect these
lines, you must carefully inspect your code and understand why the
recursion never terminates. -
If you have verified that the recursion
is implemented correctly, you can increase the stack’s size, in
order to allow a larger number of invocations. Depending on the Java
Virtual Machine (JVM) installed, the default thread stack size may
equal to either 512 KB, or 1 MB. You can increase the thread stack
size using the-Xss
flag. This flag can be specified either via the
project’s configuration, or via the command line. The format of the
-Xss
argument is:
-Xss<size>[g|G|m|M|k|K]
To describe this, first let us understand how local variables and objects are stored.
Local variable are stored on the stack:
If you looked at the image you should be able to understand how things are working.
When a function call is invoked by a Java application, a stack frame is allocated on the call stack. The stack frame contains the parameters of the invoked method, its local parameters, and the return address of the method. The return address denotes the execution point from which, the program execution shall continue after the invoked method returns. If there is no space for a new stack frame then, the StackOverflowError
is thrown by the Java Virtual Machine (JVM).
The most common case that can possibly exhaust a Java application’s stack is recursion. In recursion, a method invokes itself during its execution. Recursion is considered as a powerful general-purpose programming technique, but it must be used with caution, to avoid StackOverflowError
.
An example of throwing a StackOverflowError
is shown below:
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);
}
}
In this example, we define a recursive method, called recursivePrint
that prints an integer and then, calls itself, with the next successive integer as an argument. The recursion ends until we pass in 0
as a parameter. However, in our example, we passed in the parameter from 1 and its increasing followers, consequently, the recursion will never terminate.
A sample execution, using the -Xss1M
flag that specifies the size of the thread stack to equal to 1 MB, is shown below:
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)
...
Depending on the JVM’s initial configuration, the results may differ, but eventually the StackOverflowError
shall be thrown. This example is a very good example of how recursion can cause problems, if not implemented with caution.
How to deal with the StackOverflowError
-
The simplest solution is to carefully inspect the stack trace and
detect the repeating pattern of line numbers. These line numbers
indicate the code being recursively called. Once you detect these
lines, you must carefully inspect your code and understand why the
recursion never terminates. -
If you have verified that the recursion
is implemented correctly, you can increase the stack’s size, in
order to allow a larger number of invocations. Depending on the Java
Virtual Machine (JVM) installed, the default thread stack size may
equal to either 512 KB, or 1 MB. You can increase the thread stack
size using the-Xss
flag. This flag can be specified either via the
project’s configuration, or via the command line. The format of the
-Xss
argument is:
-Xss<size>[g|G|m|M|k|K]
From Wikipedia, the free encyclopedia
In software, a stack overflow occurs if the call stack pointer exceeds the stack bound. The call stack may consist of a limited amount of address space, often determined at the start of the program. The size of the call stack depends on many factors, including the programming language, machine architecture, multi-threading, and amount of available memory. When a program attempts to use more space than is available on the call stack (that is, when it attempts to access memory beyond the call stack’s bounds, which is essentially a buffer overflow), the stack is said to overflow, typically resulting in a program crash.[1]
Causes[edit]
Infinite recursion[edit]
The most-common cause of stack overflow is excessively deep or infinite recursion, in which a function calls itself so many times that the space needed to store the variables and information associated with each call is more than can fit on the stack.[2]
An example of infinite recursion in C.
int foo() { return foo(); }
The function foo, when it is invoked, continues to invoke itself, allocating additional space on the stack each time, until the stack overflows resulting in a segmentation fault.[2] However, some compilers implement tail-call optimization, allowing infinite recursion of a specific sort—tail recursion—to occur without stack overflow. This works because tail-recursion calls do not take up additional stack space.[3]
Some C compiler options will effectively enable tail-call optimization; for example, compiling the above simple program using gcc with -O1
will result in a segmentation fault, but not when using -O2
or -O3
, since these optimization levels imply the -foptimize-sibling-calls
compiler option.[4] Other languages, such as Scheme, require all implementations to include tail-recursion as part of the language standard.[5]
Very deep recursion[edit]
A recursive function that terminates in theory but causes a call stack buffer overflow in practice can be fixed by transforming the recursion into a loop and storing the function arguments in an explicit stack (rather than the implicit use of the call stack). This is always possible because the class of primitive recursive functions is equivalent to the class of LOOP computable functions. Consider this example in C++-like pseudocode:
void function (argument) { if (condition) function (argument.next); } |
stack.push(argument); while (!stack.empty()) { argument = stack.pop(); if (condition) stack.push(argument.next); } |
A primitive recursive function like the one on the left side can always be transformed into a loop like on the right side.
A function like the example above on the left would not be a problem in an environment supporting tail-call optimization; however, it is still possible to create a recursive function that may result in a stack overflow in these languages. Consider the example below of two simple integer exponentiation functions.
int pow(int base, int exp) { if (exp > 0) return base * pow(base, exp - 1); else return 1; } |
int pow(int base, int exp) { return pow_accum(base, exp, 1); } int pow_accum(int base, int exp, int accum) { if (exp > 0) return pow_accum(base, exp - 1, accum * base); else return accum; } |
Both pow(base, exp)
functions above compute an equivalent result, however, the one on the left is prone to causing a stack overflow because tail-call optimization is not possible for this function. During execution, the stack for these functions will look like this:
pow(5, 4) 5 * pow(5, 3) 5 * (5 * pow(5, 2)) 5 * (5 * (5 * pow(5, 1))) 5 * (5 * (5 * (5 * pow(5, 0)))) 5 * (5 * (5 * (5 * 1))) 625 |
pow(5, 4) pow_accum(5, 4, 1) pow_accum(5, 3, 5) pow_accum(5, 2, 25) pow_accum(5, 1, 125) pow_accum(5, 0, 625) 625 |
Notice that the function on the left must store in its stack exp
number of integers, which will be multiplied when the recursion terminates and the function returns 1. In contrast, the function at the right must only store 3 integers at any time, and computes an intermediary result which is passed to its following invocation. As no other information outside of the current function invocation must be stored, a tail-recursion optimizer can «drop» the prior stack frames, eliminating the possibility of a stack overflow.
Very large stack variables[edit]
The other major cause of a stack overflow results from an attempt to allocate more memory on the stack than will fit, for example by creating local array variables that are too large. For this reason some authors recommend that arrays larger than a few kilobytes should be allocated dynamically instead of as a local variable.[6]
An example of a very large stack variable in C:
int foo() { double x[1048576]; }
On a C implementation with 8 byte double-precision floats, the declared array consumes 8 megabytes of data; if this is more memory than is available on the stack (as set by thread creation parameters or operating system limits), a stack overflow will occur.
Stack overflows are made worse by anything that reduces the effective stack size of a given program. For example, the same program being run without multiple threads might work fine, but as soon as multi-threading is enabled the program will crash. This is because most programs with threads have less stack space per thread than a program with no threading support. Because kernels are generally multi-threaded, people new to kernel development are usually discouraged from using recursive algorithms or large stack buffers.[7]
See also[edit]
- Buffer overflow
- Call stack
- Heap overflow
- Stack buffer overflow
- Double fault
References[edit]
- ^ Burley, James Craig (1991-06-01). «Using and Porting GNU Fortran». Archived from the original on 2012-02-06.
- ^ a b What is the difference between a segmentation fault and a stack overflow? at StackOverflow
- ^ «An Introduction to Scheme and its Implementation». 1997-02-19. Archived from the original on 2007-08-10.
- ^ «Using the GNU Compiler Collection (GCC): Optimize Options». Retrieved 2017-08-20.
- ^ Richard Kelsey; William Clinger; Jonathan Rees; et al. (August 1998). «Revised5 Report on the Algorithmic Language Scheme». Higher-Order and Symbolic Computation. 11 (1): 7–105. doi:10.1023/A:1010051815785. S2CID 14069423. Retrieved 2012-08-09.
- ^ Feldman, Howard (2005-11-23). «Modern Memory Management, Part 2».
- ^ «Kernel Programming Guide: Performance and Stability Tips». Apple Inc. 2014-05-02.
External links[edit]
- The reasons why 64-bit programs require more stack memory Archived 2017-11-04 at the Wayback Machine
- Definition
- Run-time Stack
- Consequences of the Bug
- Reasons for the Bug
- Examples
- Conclusion
- References
Definition
A stack overflow is a run-time software bug when a program attempts to use more space than is available on the run-time stack, which typically results in a program crash.
Run-time Stack
A run-time stack is a special area of computer memory that works on the LIFO principle (Last in, first out: the last element added to a structure must be the first one to be removed). The word «stack» refers to the manner in which several plates are stacked: you form a stack by putting the plates on top of each other (this way of adding an object into the stack is called «push») and then remove them starting with the top plate (this way of removing an object from the stack is known as «pop»). Run-time stack is also known as call stack, execution stack and machine stack (these terms are used in order not to mix it up with the «stack» as an abstract data structure).
The purpose of a stack is to allow the programmer to conveniently arrange calls of subroutines. A stack can be used to store arguments to be passed to a function being called and its local variables. If another function is called by the first one, it can pop the arguments from the stack and use them, as well as store its own variables in a memory area allocated for this function. As it returns control, it also clears and frees the stack memory. High-level language programmers usually don’t bother about such things, for the task of generating the necessary routine code is performed solely by the compiler.
Consequences of the Bug
We have finally got close to the subject of our discussion. As an abstraction, a stack is an infinite storage you can endlessly add new items into. Unfortunately, everything is finite in the real world — stack memory is no exception. What will happen when it runs out as new arguments are being pushed into it or when a function allocates memory to store its variables?
An error known as a stack overflow will occur. Since a stack is used to arrange calls of user subroutines (and most programs written in contemporary programming languages — including object-oriented ones — actively employ functions one way or another), the program won’t be able to call any function after the error occurs. When that happens, the operating system takes control back, clears the stack and terminates the program. Here lies the difference between the buffer overflow and the stack overflow. The former occurs when the program attempts to access a memory area outside the buffer’s boundary and remains unnoticed if there is no protection against that; the program goes on to run correctly if lucky enough. It’s only when there is memory protection that a segmentation fault occurs. But when a stack overflow occurs, the program inevitably crashes.
To be most precise, this scenario is only true for native languages. The virtual machine in managed languages has its own stack for managed programs, which is easier to monitor, so that you can even afford throwing an exception when a stack overflow occurs. But C and C++ cannot afford such a «luxury».
Reasons for the Bug
What are the reasons for this unpleasant error? Keeping in mind the above described mechanism, we can name one: too many embedded function calls. This scenario is especially probable when using recursion: it is actually this error that infinite recursion terminates with (when there is no lazy evaluation mechanism), unlike an infinite loop which may be useful at times. However, when there is a very small area of memory allocated on the stack (which is, for instance, typical of microcontrollers), just a short sequence of calls will do the job.
Another reason is local variables requiring too much stack memory. It’s a bad idea to create a local array of a million of items or a million local variables (just in case). Just one call of such a «greedy» function may easily trigger a stack overflow. If you want to get large data amounts, you’d better use dynamic memory to be able to process an error in case it runs out.
Dynamic memory, however, is quite slow to allocate and free (because it is managed by the operating system). Besides, you have to manually allocate and release it when provided with a direct access. Conversely, stack memory is allocated very quickly (in fact you just need to change the value of one register); moreover, objects allocated on the stack are automatically destroyed when the function returns control and clears the stack. You naturally can’t help the urge to exploit it. Therefore, the third reason for the bug is manual allocation of stack memory by the programmer. The C library provides the special function alloca for this purpose. An interesting thing is that while the malloc function (intended to allocate dynamic memory) has a «sibling» that frees it (the free function), the alloca function has none: memory is freed automatically once the function returns control. This thing is likely to complicate the issue, for you can’t free memory before leaving the function. Though the man page for the alloca function clearly reads that it «is machine- and compiler-dependent; on many systems it cannot be used properly and may cause errors; its use is discouraged», programmers still use it.
Examples
As an example, let’s study a code fragment performing recursive file search (taken from MSDN):
void DirSearch(String* sDir)
{
try
{
// Find the subfolders in the folder that is passed in.
String* d[] = Directory::GetDirectories(sDir);
int numDirs = d->get_Length();
for (int i=0; i < numDirs; i++)
{
// Find all the files in the subfolder.
String* f[] = Directory::GetFiles(d[i],textBox1->Text);
int numFiles = f->get_Length();
for (int j=0; j < numFiles; j++)
{
listBox1->Items->Add(f[j]);
}
DirSearch(d[i]);
}
}
catch (System::Exception* e)
{
MessageBox::Show(e->Message);
}
}
This function receives the list of items in the specified folder and then recursively calls itself over those items which are folders. If the file tree is deep enough, the result of this is quite obvious.
Here is an example to illustrate the second reason taken from the question «What might be the reason for a stack overflow exception?» asked at Stack Overflow (this is a question-and-answer website dealing with all programming-related topics, not only stack overflow, as one may conclude from its name):
#define W 1000
#define H 1000
#define MAX 100000
//...
int main()
{
int image[W*H];
float dtr[W*H];
initImg(image,dtr);
return 0;
}
As you can see, the main function asks for some stack memory to be allocated for an int-array and a float-array, one million items each, which gives just a bit less than 8 Mbytes in total. If we recall that Visual C++ by default reserves only 1 Mbyte for the stack, we can easily answer that question.
And, finally, here is an example taken from the GitHub-repository of the Flash-player Lightspark project:
DefineSoundTag::DefineSoundTag(/* ... */)
{
// ...
unsigned int soundDataLength = h.getLength()-7;
unsigned char *tmp = (unsigned char *)alloca(soundDataLength);
// ...
}
One may hope that h.getLength()-7 won’t grow too big and no overflow will occur in the next line. But is the time saved on memory allocation worth a potential crash?
Conclusion
Stack Overflow is a fatal error which is most often found in programs containing recursive functions. It can also be caused by pushing too many local variables into the stack or manual allocation of stack memory. Stick to the old good rules: when you have a choice, prefer iteration to recursion, and avoid manual interference in what the compiler does better.
References
- A. Tanenbaum. Structured Computer Organization.
- Wikipedia. Stack Overflow.
- man 3 alloca.
- MSDN. How to recursively search folders by using Visual C++.
- Stack Overflow. Stack Overflow C++.
- GitHub. Lightspark — «tags.cpp».
We can email you a selection of our best articles once a month
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:
public class SimpleExample {
public static void main(String args[]) {
a()
}
public static void a() {
int x = 0;
b();
}
public static void b() {
Car y = new Car();
c();
}
public static void c() {
float z = 0f;
System.out.println("Hello");
}
}
This program is very simple with the following execution code:
-
main()
method is invoked first -
main()
method invokesa()
method. Insidea()
method, the integer variable ‘x’ is initialized to value 0. -
a()
method in turn invokesb()
method. Insideb()
method, the Car object is constructed and assigned to variable ‘y.’ -
b()
method in turn invokes thec()
method. Inside thec()
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
:
public class SOFDemo {
public static void a() {
// Buggy line. It will cause method a() to be called infinite number of times.
a();
}
public static void main(String args[]) {
a();
}
}
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
:
Exception in thread "main" java.lang.StackOverflowError
at com.buggyapp.stackoverflow.SOFDemo.a(SOFDemo.java:7)
at com.buggyapp.stackoverflow.SOFDemo.a(SOFDemo.java:7)
at com.buggyapp.stackoverflow.SOFDemo.a(SOFDemo.java:7)
at com.buggyapp.stackoverflow.SOFDemo.a(SOFDemo.java:7)
at com.buggyapp.stackoverflow.SOFDemo.a(SOFDemo.java:7)
at com.buggyapp.stackoverflow.SOFDemo.a(SOFDemo.java:7)
at com.buggyapp.stackoverflow.SOFDemo.a(SOFDemo.java:7)
at com.buggyapp.stackoverflow.SOFDemo.a(SOFDemo.java:7)
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:
-Xss2m
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.
JVM version |
Thread stack size |
Sparc 32-bit JVM |
512k |
Sparc 64-bit JVM |
1024k |
x86 Solaris/Linux 32-bit JVM |
320K |
x86 Solaris/Linux 64-bit JVM |
1024K |
Windows 32-bit JVM |
320K |
Windows 64-bit JVM |
1024K |
Data Types
Frame (networking)
Java (programming language)
Java virtual machine
32-bit
64-bit
Primitive data type
Opinions expressed by DZone contributors are their own.