如何查看StackOverflowError的堆栈跟踪的开始

时间:2019-02-15 09:29:45

标签: java exception stack-overflow

我正在调查Java应用程序正在产生的StackOverflowError。堆栈跟踪看起来像这样(对不起,我不能共享实际的生产跟踪):

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)
...

从代码检查和单元测试来看,函数foo本身似乎是正确的,这表明传递给foo的数据存在问题。

问题在于,堆栈大小大于异常中堆栈跟踪的限制。这意味着未显示堆栈跟踪的开始,这使得进一步研究非常困难。

如何获取Java以显示堆栈跟踪的开始?

我相信可以将Java配置为减小堆栈大小或增加异常限制。但是,我担心在生产中调整这些值的其他影响。例如,如果Java使用堆栈中的前50个和后50个调用,那将更有帮助。

2 个答案:

答案 0 :(得分:1)

如果您可以编辑生产代码并在某个地方运行它;您可以修改代码,以使stacktrace以特定的递归深度转储。这样您就可以“查看堆栈跟踪的底部”。

当然,您需要以兼容的方式修改生产代码(例如,不允许在foo方法中添加“ depth”参数,因为这会影响您的客户)。

参见例如以下代码;我们将递归深度存储在线程局部变量中。

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);
      }
    }
  }
}

使用java -Dmy.debug.dump.enabled=true lang.StackOverflowTest运行它,输出为:

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

可以通过多种方式进行调整(例如,更改递归深度);或仅对整个程序执行一次转储(因为您可能有多个转储)。

答案 1 :(得分:0)

您似乎在想,堆栈溢出错误就像本机程序中的缓冲区溢出异常一样,当存在写入尚未分配给该缓冲区的内存的风险,从而损坏了其他一些内存位置时。根本不是这样。

JVM为每个线程的每个堆栈分配了给定的内存,并且如果尝试调用方法的尝试恰巧填充了该内存,则JVM会引发错误。就像您试图在长度为N的数组的索引N处写入时一样。不会发生内存损坏。堆栈无法写入堆。

对于堆栈来说,StackOverflowError就是对堆的OutOfMemoryError:它只是表示没有可用的内存了。