StackOverflowError in Java

Learn via video course
FREE
View all courses
Java Course - Mastering the Fundamentals
Java Course - Mastering the Fundamentals
by Tarun Luthra
1000
5
Start Learning
Java Course - Mastering the Fundamentals
Java Course - Mastering the Fundamentals
by Tarun Luthra
1000
5
Start Learning
Topics Covered

Overview

A stack is a type of memory. On this stack, the local automatic variable is created, and method arguments are passed. When a process begins, it is given a default stack size that is fixed for each process. When we call a method, we establish a new stack frame on the call stack or the thread stack size. This stack frame contains the parameters of the invoked method, mostly the local variables and the method's return address. These stack frames will be created iteratively and will be terminated only when the end of the method invokes is reached in the nested methods. If the JVM runs out of space for the new stack frames that must be created during this operation, a StackOverflowError will be thrown. The two most common causes of stack overflow are deep or infinite recursion and cyclic relationships.

Introduction to StackOverflowError in Java

The java.lang.StackOverflowError is a runtime error that indicates major problems that an application cannot detect. The java.lang.StackOverflowError indicates that the application stack has been exhausted, which is frequently the result of deep or infinite recursion. It extends the VirtualMachineError class, which signals that the JVM (Java Virtual Machine) has failed or has run out of resources and is unable to run.

For example, we have a function like this:

In the preceding code, recursiveFunction() will keep calling itself, getting deeper and deeper, until the space needed to keep track of which functions you're in is exhausted, resulting in the stackoverflow error. A faulty recursive call is a common cause of a stack overflow. Typically, this occurs when your recursive functions lack the proper termination condition, causing it to call itself repeatedly.

When we call a method, a new stack frame is formed on the call stack or the thread stack size. This stack frame is used to allocate parameters and local variables. The stack is normally located at the top of your address space, and as it is exhausted, it moves to the bottom. Your process also contains a heap, which is located at the bottom of the process. This heap might extend to the upper end of your address space as you allocate memory. As you can see, the heap and stack have the potential to "collide." If there is no room for a new stack frame, the Java Virtual Machine throws a StackOverflowError (JVM).

  • You can't push if the stack is full; if you try, you'll get a stack overflow error.
  • You can't pop if the stack is empty; if you try, you'll get a stack underflow error.

Declaration

classDescription
java.lang.ObjectThe java.lang.Object class is at the top of the class hierarchy. Every class has an Object as a superclass. The methods of this class are implemented by all objects, including arrays.
java.lang.ThrowableThe java.lang.Throwable is the superclass of all Java errors and exceptions. Only objects that are instances of this class (or one of its subclasses) are thrown by the JVM or can be thrown by the Java throw statement.
java.lang.ErrorThe java.lang.Error is the child class of the Throwable class that specifies serious errors that a reasonable application should not try to catch.
java.lang.VirtualMachineErrorThe VirtualmachineError is thrown when the JVM detects an internal fault or resource restriction that prohibits it from operating. JVM uses this self-defensive feature to stop the entire application from crashing.

Constructors Summary of StackOverflowError

ConstructorSyntaxDescription
stackOverflowError()public StackOverflowError()Creates an instance of the StackOverflowError class with the message null.
stackOverflowError (String s)public StackOverflowError(String s)Creates an instance of the StackOverflowError class, with the provided string as the message. The string argument represents the name of the class that generated the error.

Methods Summary of StackOverflowError

Methods inherited from class java.lang.Throwable

MethodSyntaxDescription
addSuppressed()public final void addSuppressed?(Throwable exception)The addSuppressed() method was used to append the exception to the exceptions that were suppressed to deliver this exception. This method is thread-safe. It is often invoked by a try-catch clause. The suppression behavior of a Throwable is enabled unless disabled using a constructor and when suppression is deactivated, this function does nothing but validate its argument. When we have two exceptions in a try-finally block, the first exception is ignored and the exception from the final block is displayed by default. In other cases, the final block is used to close the resource, and in those cases, we want to see the original exception, along with some exceptions from the final block, that closed our resource and failed, so we can add those exceptions that were suppressed using this method. If there are many sibling exceptions and only one of them can be propagated, this method can be used to do so.
fillInStackTrace()public Throwable fillInStackTrace()The fillInStackTrace() method is considered an important method of the Throwable class in Java. The stack trace can help determine where exactly the exception is thrown. In some cases, we may need to rethrow an exception and determine where it was thrown there we can use the fillInStackTrace() method.
getCause()public Throwable getCause()The getCause() method returns the cause of the exception or returns null if the cause of the exception is unknown. This method does not accept any arguments and does not throw any exceptions. It returns the cause specified by one of its constructors or that was decided by the formation of the initCause() method of the Throwable class.
getLocalizedMessage()public String getLocalizedMessage()When an exception occurs, the getLocalizedMessage() method is used to retrieve a locale-specific description of the Throwable object. It enables us to change the description of the Throwable object based on the locally Specific message. The default implementation of this method gives the same result as getMessage() for subclasses that do not override it.
getMessage()public String getMessage()The getMessage() method is used to return a descriptive message of the Throwable object, which can also be null. This method returns the exception's detail message as a string value.
getStackTrace()public StackTraceElement[] getStackTrace()The getStackTrace() method returns an array of stack trace components, each representing a single stack frame. The array's zeroth element (assuming the array's length is non-zero) represents the top of the stack, which is the sequence's final method invocation. At this point, this throwable was created and thrown. The last element of the array represents the bottom of the stack, which is the first method invocation in the sequence (provided the array's length is non-zero).
getSuppressed()public final Throwable[] getSuppressed()The getSuppressed() method returns an array containing all of the exceptions suppressed to deliver this exception. This suppression is often performed by the try-with-resources statement. To deliver an Exception, an empty array of suppressed exceptions is returned if no exceptions were suppressed or if suppression is disabled. Any changes to the returned array have no impact on future calls to this method.
initCause()public Throwable initCause?(Throwable cause)The initCause() method is used to initialize the cause of this Throwable with the cause supplied as an argument to initCause (). When an exception occurs, the reason is the throwable that caused this throwable Object to be thrown. This method can only be invoked once. Generally, t his method is often called from within the constructor or immediately after the throwable is created. This method cannot be called more than once if the caller Throwable is created with Throwable(Throwable) or Throwable(String, Throwable).
printStackTrace()public void printStackTrace()The printStackTrace() method is used to print this Throwable as well as other details such as the class name and line number where the exception occurred, which is its backtrace. This method prints the Throwable object stack trace to the standard error output stream. For example, if one of five methods in your code throws an exception, printStackTrace() will take you to the exact line in which the method exception was thrown. The first line of output displays the same string produced by the toString() method for this object, which means Exception class name, and the following lines represent data previously collected by the method fillInStackTrace ().
setStackTrace()public void setStackTrace(StackTraceElement[] stackTrace)The setStackTrace() method specifies the stack trace items that will be returned by getStackTrace() and printed by printStackTrace() and related methods. This method allows the client to override the default stack trace that is created by fillInStackTrace() when a throwable is constructed or deserialized when a throwable is read from a serialization stream. If the stack trace of this Throwable is not writable, invoking this method has no impact other than validating its argument.
toString()String toString() or static String toString(int i)The toString() method returns a String object representing the value of the Number Object. If a primitive data type is passed as an argument, the String object representing the primitive data type value is returned. If the method accepts two arguments, it will return a String representation of the first argument in the radix given by the second argument.

Methods inherited from class java.lang.Object

MethodSyntaxDescription
clone()protected Object clone() throws CloneNotSupportedExceptionCloning is the process of duplicating an object. The clone() method of the Java Object class returns a copy of the existing instance. As Object is the base class in Java, all objects allow cloning by default.
equals()public boolean equals(Object anotherObject)The equals() method compares two items and returns true if they are logically equivalent. Since equals() is a method defined in the Object class, the default implementation compares object references or memory locations where the objects are stored in the heap. As a result, the equals() method checks the object by default with the ==== operator.
finalize()protected void finalize() throws ThrowableThe finalize() method is used to do cleanup before destroying an object. The garbage collector calls it before deleting items from memory. The finalize() method is invoked by default for every object before it is deleted. This method assists the Garbage Collector in closing all resources used by the object and helps JVM in-memory optimization.
getClass()public final Class<?> getClass()The getClass() method returns the runtime class of this object. The class object returned is the object locked by the static synchronized method of the represented class.
hashCode()public int hashCode()The hashCode() method returns the hash values of the given input objects. It returns an integer with the hash value of the input object as its value. It is used to generate the object hash values. Using these hash values, these objects are kept in Java collections such as HashMap, HashSet, and HashTable.
notify()public final void notify()The notify() method is used to wake up a single thread. This method sends a notification to only one thread that is waiting for a specific object. When we use the notify() method and several threads are waiting for the notification, then only one thread receives the notification and the other thread must wait for further notifications.  It is used in combination with the wait() method to communicate across threads, as a thread that enters the waiting-for state via the wait() method will remain there until another thread calls either the notify() or notifyAll() method.
notifyAll()public final void notifyAll()The notifyAll() method is used to wake up all threads. This method sends a notification to all waiting threads of a specific object. If we use the notifyAll() method and numerous threads are waiting for the notice, all threads will receive the notification, but execution will be conducted one by one since each thread requires a lock, and only one lock is available for one object.
wait()public final void wait()The wait() method makes the current thread wait until another thread calls the notify() or notifyAll() methods for this object. In other words, this method behaves exactly as if it were simply performing the call wait(0).

What does java.lang.StackOverflowError occur in Java?

The java.lang.StackOverflowError arises when the application stack keeps expanding until it exceeds its maximum size. The following are some of the most typical reasons for the occurrence of java.lang.StackOverflowError:

1) Deep or infinite recursion: When the function keeps calling itself, it is said to be an infinite recursion. Every recursive function needs to have a halting condition or the point at which the function stops calling itself.

2) Cyclic relationships: Cyclic relationships between classes occur when class A1 instantiates an object of class A2, which in turn instantiates an object of class A1. This type of relationship can be considered a type of recursion.

3) Memory-intensive applications: These types of applications rely on resource-intensive objects such as GUIs, XML documents, or Java2D classes.

Consider the following program, which will result in a java.lang.StackOverflowError:

In this program, the main() method calls the a() method. The a() method calls itself recursively. This implementation will cause the a() method to be called an infinite number of times. In this case, the a() method will be appended to the thread's stack frame an endless number of times. As a result, after a few thousand iterations, the thread's stack size limit would be reached. When the stack size limit is exceeded, it will result in java.lang.StackOverflowError:

StackOverflowError Examples

Example 1: In this example, we created a recursive method and named it  recursivePrint, which prints one number and then calls itself with the next succeeding integer as an argument. The recursion finishes when we call the method and pass 0 as a parameter. However, in our example, we begin printing numbers from 1, therefore the recursion never ends.

Output of the above code:

Example 2: In this example, we created two classes, class1, and class2. The class1 class has one instance of the class2 class, and the class2 class has one instance of the class1 class. As a result, there is a cyclic dependency between these two classes. In addition, each toString method calls the corresponding toString() method of the other class, and so on, resulting in a StackOverflowError.

Output of the above code:

How to Resolve java.lang.StackOverflowError in Java?

There are several approaches to deal with the StackOverflowError in Java.

1) Fix the code or Examine the stack trace

Examining the error stack trace carefully and looking for a repeating pattern of line numbers allows you to find the line of code with the recursive calls. After identifying the line, the code should be examined and corrected by giving a valid ending condition. Thread stack size can increase to a considerable extent due to a non-terminating recursive call. In such cases, you must correct the source code that is generating the recurrent looping. When a StackOverflowError is thrown, it prints the stack trace of the code that was being executed recursively. This code is a solid starting point for debugging and resolving the problem.

As an example, consider the following error stack trace:

In the above trace, line 9 can be seen recurring, which is where the recursive calls are made, resulting in java.lang.StackOverflowError.

2) Increase Thread Stack Size (-Xss)

If the code has been modified to use correct recursion but the application still raises a java.lang.StackOverflowError, then you should increase the thread stack size that will eventually allow for a large number of invocations. Increasing the thread stack size can be advantageous, in cases, when the program includes employing a large number of local variables or calling a large number of methods. Perhaps the thread has to perform a large number of methods or generate a large number of local variables/created in the methods the thread has been executing. In such cases, use the JVM parameter -Xss to increase the thread's stack size.

The thread stack size can be increased by modifying the -Xss parameter on the JVM, which is set when the application is started. The below argument must be passed when the application is launched :

The above line will increase the thread's stack size to 2 megabytes, preventing the JVM from reporting a java.lang.StackOverflowError.

The size of the default thread stack varies depending on your Java version, operating system, and vendor.

JVM versionThread stack size
Sparc 32-bit JVM512k
Sparc 64-bit JVM1024k
x86 Solaris/Linux 32-bit JVM320K
x86 Solaris/Linux 64-bit JVM1024K
Windows 32-bit JVM320K
Windows 64-bit JVM1024K

Conclusion

  • When a method is invoked, a new stack frame is added to the call stack. This stack frame contains the parameters of the invoked method, local variables, and the return address of the method, which is the point at which method execution should resume after the invoked method has returned.
  • Stack frames will be created till the number of method invocations detected inside nested methods is reached. If the JVM finds a situation in which there is insufficient space to generate a new stack frame, it will report a StackOverflowError.
  • The most common cause of this error in the JVM is unterminated/infinite recursion. It can also occur when an application continues to call methods from within methods until the stack is exhausted. Another cause is having a large number of local variables inside a method or a class being instantiated as an instance variable of that class within the same class.
  • The simplest method to encounter StackOverflowError is to carefully analyze the stack trace for a repeated pattern of line numbers. These line numbers represent the code that is being called recursively. Once you've found these lines, check for the termination condition (base condition) for the recursive calls.
  • Once you've confirmed that the recursion is working properly, you can raise the stack size to allow for more invocations. Your compiler's parameters can be changed to increase the stack size.