如何获取 StackOverflowError 的完整堆栈

2022-09-01 04:30:31

当观察StackOverflowError时,如何检索完整的调用堆栈?

考虑这个简单的例子:

public class Overflow {

    public Overflow() {
        new Overflow();
    }
    public static void a() {
        new Overflow();
    }
    public static void main(String[] argv) {
        a();
    }
}

现在报告的错误是:

Exception in thread "main" java.lang.StackOverflowError
    at Overflow.<init>(Overflow.java:11)
    [last line repeated many times]

但我在堆栈跟踪中看不到 and 方法。我的猜测是因为溢出,堆栈上的最新条目取代了最旧的条目(?)。maina

现在,如何在输出中获取和堆栈条目?amain

背景是我得到了一个StackOverflowError(但这不是无限递归,因为它在增加堆栈大小时不会发生),并且很难在代码中发现问题。我只从代码调用它的信息中获取多行,而不是信息。应用程序太复杂,无法在每次调用 s 时设置断点。java.util.regex.PatternPattern


答案 1

JVM 的人为限制为 1024 个条目,您可以在异常或错误的堆栈跟踪中拥有这些条目,这可能是为了在发生异常或错误时节省内存(因为 VM 必须分配内存来存储堆栈跟踪)。

幸运的是,有一个标志允许增加此限制。只需使用以下参数运行程序:

-XX:MaxJavaStackTraceDepth=1000000

这将打印多达 100 万个堆栈跟踪条目,这应该绰绰有余。也可以将此值设置为 at,以将条目数设置为无限制。0

此非标准 JVM 选项列表提供了更多详细信息:

最大编号堆栈跟踪中 Java 异常的行数(0 表示全部)。对于 Java > 1.6,值 0 实际上表示 0。必须指定值 -1 或任何负数才能打印所有堆栈(在 Windows 上使用 1.6.0_22、1.7.0 进行测试)。对于Java <= 1.5,值0表示一切,JVM扼流于负数(在Windows上使用1.5.0_22测试)。

使用此标志运行问题示例会得到以下结果:

Exception in thread "main" java.lang.StackOverflowError
    at Overflow.<init>(Overflow.java:3)
    at Overflow.<init>(Overflow.java:4)
    at Overflow.<init>(Overflow.java:4)
    at Overflow.<init>(Overflow.java:4)
(more than ten thousand lines later:)
    at Overflow.<init>(Overflow.java:4)
    at Overflow.<init>(Overflow.java:4)
    at Overflow.a(Overflow.java:7)
    at Overflow.main(Overflow.java:10)

这样,即使实际的堆栈跟踪长度超过 1024 行,也可以找到引发 Error 的代码的原始调用方。

如果您无法使用该选项,那么还有另一种方法,如果您处于这样的递归函数中,并且如果您可以修改它。如果添加以下尝试捕获:

public Overflow() {
    try {
        new Overflow();
    }
    catch(StackOverflowError e) {
        StackTraceElement[] stackTrace = e.getStackTrace();
        // if the stack trace length is at  the limit , throw a new StackOverflowError, which will have one entry less in it.
        if (stackTrace.length == 1024) {
            throw new StackOverflowError();
        }
        throw e;  // if it is small enough, just rethrow it.
    }
}

从本质上讲,这将创建并抛出一个新的,丢弃最后一个条目,因为与前一个条目相比,每个条目都会被发送一个级别(这可能需要几秒钟,因为必须创建所有这些错误)。当堆栈跟踪将减少到 1023 个元素时,只需将其重新推。StackOverflowError

最终,这将在堆栈跟踪的底部打印 1023 行,这不是完整的堆栈跟踪,但可能是其中最有用的部分。


答案 2

据我所知,不可能获得完整的堆栈跟踪(但是,我真的不知道为什么)。

但是,为了跟踪问题,您可以手动检查受影响代码中的堆栈深度,如下所示:

StackTraceElement[] trace = Thread.currentThread().getStackTrace();
if (trace.length > SOME_VALUE) {
  // trigger some diagnostic action, print a stack trace or have a breakpoint here
}

SOME_VALUE需要通过实验找到(足够高,在“好”的情况下不会被触发,足够低,不会无法到达)。当然,这会减慢代码的速度,并且只应用于调试问题。

更新:我似乎错过了问题发生在 中,这使事情复杂化。但是,您可以在堆栈跟踪中的某个方法上使用条件方法断点,条件如下(实际值可能需要调整):PatternPattern

Thread.currentThread().getStackTrace().length > 300

这样,当您遇到断点时,您可以在堆栈底部找到自己的代码。


推荐