Is it safe to catch a StackOverflowError in Java?

I have two different implementations of a function (e.g. tree size) which is recursive and the other is an explicit stack.

Recursive is very fast (probably because it does not need to allocate anything on the heap), but can cause a stack overflow on some "rare" inputs (in the tree example, this will be on any unbalanced tree). The explicit version is slower, but is unlikely to cause a stack overflow.

How safe is it to use the default recursive implementation and restore the StackOverflowError from the exception by doing explicit?

Is this considered bad practice?

Here is a small code example:

interface Node { List<? extends Node> getSons(); } static int sizeRec (Node root) { int result = 1; for (Node son : root.getSons()) { result += sizeRec(son); } return result; } static int sizeStack (Node root) { Stack<Node> stack = new Stack<Node>(); stack.add(root); int size = 0; while (! stack.isEmpty()) { Node x = stack.pop(); size ++; for (Node son : x.getSons()) { stack.push(son); } } return size; } static int size (Node root) { try { return sizeRec(root); } catch (StackOverflowError e) { return sizeStack(root); } } 
+6
source share
2 answers

Well, that is a matter of opinion. But I do not believe that you should do it. Firstly, your logic distorts the meaning of exception handling (exceptions for "exceptions" are not for logic), other programmers will have problems interpreting your code.

In addition, you should not catch "Erros", which indicate problems while working in the environment. You should ask yourself if you should forget about good practices. Perhaps you could try to tweak the runtime configuration to fit the application or add additional validation logic ... your call is there ... but since security is being considered, you cannot say that everything is fine, as we don’t know like the state of the stack now, and different JRE implementations may vary there.

Finally, for the question at the bottom: this is bad practice, and it is not safe.

Quote from https://softwareengineering.stackexchange.com/questions/209099/is-it-ever-okay-to-catch-stackoverflowerrorror-in-java :

Of course, there are situations when a stack overflow can leave an application inconsistent, like running out of memory. Imagine that some object is constructed and then initialized using nested calls of internal methods - if one of them throws, the object may well be in a state that should not be possible, just as if the distribution was unsuccessful. But this does not mean that your decision cannot be the best.

This has a reason to be called Error, not Exception ...

From the docs:

open abstract class VirtualMachineError extends Error: It is thrown to indicate that the Java virtual machine is damaged or it does not have enough resources necessary to continue working

public class Error extends Throwable: Error is a subclass of Throwable that indicates serious problems that a reasonable application should not try to catch. Most of these errors are abnormal conditions. The ThreadDeath error, although it is a β€œnormal” condition, is also a subclass of Error, since most applications should not try to catch it. The method is not required to declare in the throws property any subclasses of Error that may be thrown during the execution of the method, but not caught, because these errors are abnormal conditions that should never occur. That is, the error and its subclasses are considered as unchecked exceptions for the purpose of checking compile-time exceptions.

+3
source

I would suggest that in your sizeRecursive method you maintain a stack depth counter, and if you exceed the specified level, switch to the sizeStackUsingHeap method. Do not rely on StackOverflow Exception - this is bad practice. You should not use exceptions to define your algorithm.

 interface Node { List<? extends Node> getSons(); } // Switch to a heap stack if the stack ever hits this level. private static final int STACKLIMIT = 1000; private static int sizeRecursive(Node root) { // Start the stack depth at 0. return sizeRecursive(root, 0); } // Recursive implementation. private static int sizeRecursive(Node root, int depth) { int result = 1; for (Node son : root.getSons()) { if (depth < STACKLIMIT) { result += sizeRecursive(son, depth + 1); } else { // Too deep - switch to heap. result += sizeUsingHeap(son); } } return result; } // Use this when the stack gets realy deep. It maintains the stack in the heap. private static int sizeUsingHeap(Node root) { Stack<Node> stack = new Stack<>(); stack.add(root); int size = 0; while (!stack.isEmpty()) { // I am assuming this algorithm works. Node x = stack.pop(); size++; for (Node son : x.getSons()) { stack.push(son); } } return size; } // Always use sizeRecursive to begin with. public static int size(Node root) { return sizeRecursive(root); } 
+6
source

Source: https://habr.com/ru/post/982577/


All Articles