-1

I am investigating a StackOverflowError that a Java application is producing. The stack trace looks like this (sorry, I can't share the actual production trace):

at test.StackOverflowTest.foo(StackOverflowTest.java:24)
at test.StackOverflowTest.foo(StackOverflowTest.java:24)
at test.StackOverflowTest.foo(StackOverflowTest.java:24)
at test.StackOverflowTest.foo(StackOverflowTest.java:24)
...

The function foo itself appears to be correct from code inspection and unit tests, indiciating this is a problem with data passed to foo.

The problem is that the stack size is larger than the limit on the stack trace within an exception. This means the beginning of the stack trace is not shown, making further investigation very difficult.

How can I get Java to show the beginning of the stack trace?

I believe Java can be configured to either reduce the stack size or increase the exception limit. However, I am concerned about other impacts of tweaking those values in production. It would be much more helpful if Java would, say, use the first 50 and last 50 calls on the stack.

paj28
  • 2,210
  • 3
  • 25
  • 37
  • There is no such thing as "limit on the stack trace within an exception". It is probably your loging library problem. – talex Feb 15 '19 at 09:33
  • 1
    Is there any recursive method in your code? – nice_dev Feb 15 '19 at 09:34
  • Could you share the implicated function, please? – Ctorres Feb 15 '19 at 10:25
  • @talex - there is a limit. See [this question](https://stackoverflow.com/questions/3418134/how-to-avoid-the-1024-lines-of-java-exception-stack-limit) – paj28 Feb 15 '19 at 11:24
  • @Ctorres - It's java.util.Collections.UnmodifiableCollection.iterator – paj28 Feb 15 '19 at 11:25
  • @Raedwald - I've edited to make the question clearer. Not a dupe of link 1 because I already know what a stack trace is, and this specific example is missing key information. Not a dupe of link 2 because this is occurring in production and I can't reproduce within a debugger. – paj28 Feb 15 '19 at 18:03
  • @vivek_23 - This is happening in java.util.Collections.UnmodifiableCollection.iterator which ordinarily shouldn't recurse due to polymorphism. I agree the stack trace strongly implies uncontrolled recursion. – paj28 Feb 15 '19 at 18:06
  • 1
    @Raedwald - Someone pointed me to [this question](https://stackoverflow.com/questions/3418134/how-to-avoid-the-1024-lines-of-java-exception-stack-limit) which is a closer match. Happy to have this closed as dupe of that question. – paj28 Feb 16 '19 at 17:18
  • I added it to the list of duplicates. – Mark Rotteveel Feb 17 '19 at 12:22
  • @MarkRotteveel - Ok. Suggest you remove the original two dupes as they're not the same question at all – paj28 Feb 17 '19 at 15:30
  • On the other hand, they also don't hurt to be there. – Mark Rotteveel Feb 17 '19 at 15:32

2 Answers2

2

If you can edit your production code and run it somewhere; you could modify the code so that a stacktrace is dumped at a specific recursion depth. That allows you to "see the bottom" of the stacktrace.

Of course, you need to modify your production code in a compatible way (eg., adding a "depth" parameter to the foo method is not allowed- since this affects your clients).

See eg. the following code; we store the recursion depth in a thread-local variable.

package lang;

/**
 * run with -Dmy.debug.dump.enabled=true
 */
public class StackOverflowTest {

  public static void main(String[] args) {
    try {
      StackOverflowTest o = new StackOverflowTest();
      o.foo();
    } catch (StackOverflowError err) {
      System.out.println("err: StackOverflowError");
    }
  }

  private static ThreadLocal<Integer> recurseCount = new ThreadLocal<Integer>() {
    @Override
    protected Integer initialValue() {
      return 0;
    };
  };

  private static final boolean DUMP_ENABLED;
  static {
    String sysprop = System.getProperty("my.debug.dump.enabled");
    DUMP_ENABLED = sysprop!=null && "true".equals(sysprop);
  }
  // or set it via system properties
  private static final int DUMP_ON_RECURSION_NUM=4;

  class MyRecurseDump extends Exception {
    public MyRecurseDump(String msg) {
      super(msg);
    }
    private static final long serialVersionUID = 1L;
  }

  private void foo() {
    try {
      if (DUMP_ENABLED) {
        recurseCount.set(recurseCount.get()+1);
        if (recurseCount.get()==DUMP_ON_RECURSION_NUM) {
          new MyRecurseDump("foo: reached num="+DUMP_ON_RECURSION_NUM+" recursion depth")
            .printStackTrace(System.err);
        }
      }

      // put foo code here
      int x;
      foo();
      // end of foo code
      //*********************************************
    }
    finally {
      if (DUMP_ENABLED) {
        recurseCount.set(recurseCount.get()-1);
      }
    }
  }
}

Running it with java -Dmy.debug.dump.enabled=true lang.StackOverflowTest , the output is:

lang.StackOverflowTest$MyRecurseDump: foo: reached num=4 recursion depth
    at lang.StackOverflowTest.foo(StackOverflowTest.java:44)
    at lang.StackOverflowTest.foo(StackOverflowTest.java:53)
    at lang.StackOverflowTest.foo(StackOverflowTest.java:53)
    at lang.StackOverflowTest.foo(StackOverflowTest.java:53)
    at lang.StackOverflowTest.main(StackOverflowTest.java:11)
err: StackOverflowError

It can be tweaked in a number of ways (change recursion depth in example); or only perform a single dump across your whole program (because you could have multiple dumps).

Daniele
  • 2,672
  • 1
  • 14
  • 20
  • Nice idea, thank-you. I think I could get the same effect by reducing the stack size. Unfortunately, if I do this outside production I can't reproduce the error condition. – paj28 Feb 15 '19 at 16:00
0

It seems you're thinking that a stackoverflow error is like a buffer overflow exception in native programs, when there is a risk of writing into memory that had not been allocated for the buffer, and thus to corrupt some other memory locations. It's not the case at all.

JVM has a given memory allocated for each stack of each thread, and if an attempt to call a method happens to fill this memory, JVM throws an error. Just like it would do if you were trying to write at index N of an array of length N. No memory corruption can happen. The stack can not write into the heap.

A StackOverflowError is to the stack what an OutOfMemoryError is to the heap: it simply signals that there is no more memory available.