如何获取 StackOverflowError 的完整堆栈

发布于 2024-10-20 01:32:42 字数 829 浏览 5 评论 0原文

当观察 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]

But I can't see the main and a堆栈跟踪中的 方法。我的猜测是这是因为溢出,堆栈上的最新条目替换了最旧的条目(?)。

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

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

When observing a StackOverflowError how to retrieve the full call stack?

Consider this simple example:

public class Overflow {

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

Now the error reported is:

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

But I can't see the main and a method in the stack trace. My guess is this is because of overflow, the newest entry on the stack replaces the oldest one (?).

Now, how to get the a and main stack entries in the output?

The background is I get the a StackOverflowError (but that's not an infinite recursion, because it doesn't happen when increasing stack size) and it's hard to spot the problem in the code. I only get the multiple lines from java.util.regex.Pattern but not the information what code called that. The application is too complicated to set a breakpoint on each call to Patterns.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

毅然前行 2024-10-27 01:32:42

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

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

-XX:MaxJavaStackTraceDepth=1000000

这将打印最多 100 万条堆栈跟踪条目,这应该足够了。也可以将此值设置为0,以将条目数量设置为无限制。

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

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

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

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 行,您也可以找到引发错误的代码的原始调用者。

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

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

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

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

The JVM has an artificial limit of 1024 entries that you can have in the stack trace of an Exception or Error, probably to save memory when it occurs (since the VM has to allocate memory to store the stack trace).

Fortunately, there is a flag that allows to increase this limit. Just run your program with the following argument:

-XX:MaxJavaStackTraceDepth=1000000

This will print up to 1 million entries of your stack trace, which should be more than enough. It is also possible to set this value at 0 to set the number of entries as unlimited.

This list of non-standard JVM options gives more details:

Max. no. of lines in the stack trace for Java exceptions (0 means
all). With Java > 1.6, value 0 really means 0. value -1 or any
negative number must be specified to print all the stack (tested with
1.6.0_22, 1.7.0 on Windows). With Java <= 1.5, value 0 means everything, JVM chokes on negative number (tested with 1.5.0_22 on
Windows).

Running the sample of the question with this flag gives the following result:

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)

This way, you can find the original callers of the code that threw the Error, even if the actual stack trace is more than 1024 lines long.

If you can not use that option, there is still another way, if you are in a recursive function like this, and if you can modify it. If you add the following try-catch:

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

Essentially, this will create and throw a new StackOverflowError, discarding the last entry because each one will be sent one level up compared to the previous one (this can take a few seconds, because all these Errors have to be created). When the stack trace will be reduced to 1023 elements, it is simply rethrown.

Ultimately this will print the 1023 lines at the bottom of the stack trace, which is not the complete stack trace, but is probably the most useful part of it.

书信已泛黄 2024-10-27 01:32:42

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

但是,您可以采取的措施来追踪问题,就是手动检查受影响代码中的堆栈深度,如下所示:

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(足够高,不会在“良好”的情况并且足够低,不会无法到达)。当然,这会减慢您的代码速度,并且只能用于调试问题。

更新:我似乎忽略了问题发生在Pattern中,这使事情变得复杂。但是,您可以在堆栈跟踪中的 Pattern 方法之一处使用条件方法断点,条件如下(实际值可能需要调整):

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

这样您就可以在当您到达断点时,堆栈底部。

As far as I know, it's not possible to get the full stack trace (however, I don't really know why).

However, what you could do to track down the problem, is to manually check for the stack depth in your affected code like this:

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 would need to be found by experimentation (high enough to not be triggered in "good" situations and low enough to not be unreachable). Of course this would slow down your code and should only be used for debugging the problem.

Update: I seem to have missed that the problem occurs in Pattern, which complicates matters. However, you could use a conditional method breakpoint at one of the Pattern methods in the stack trace with a condition like this (the actual value might need tweaking):

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

This way you can find your own code at the bottom of the stack when you hit the breakpoint.

吐个泡泡 2024-10-27 01:32:42

如果堆栈不足,请考虑创建一个具有足够堆栈的专用线程,特别是用于运行请求。下面的示例代码。

package t1;

import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Pattern;

public class RegExpRunner {
    ExecutorService svc;    
    public RegExpRunner(long stackSize){
        init(stackSize);

    }


    void init(long stackSize){
        final SynchronousQueue<Runnable> queue = new SynchronousQueue<Runnable>();

        svc = new ThreadPoolExecutor(1, 2, 60, TimeUnit.SECONDS,  queue, createThreadFactory(stackSize), new RejectedExecutionHandler(){//wait if there is a concurrent compile and no available threads
            @Override
            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                try{
                    queue.put(r);
                }catch(InterruptedException _ie){
                    Thread.currentThread().interrupt();
                    throw new IllegalStateException(_ie);
                }
            }                   
        });
    }

    private ThreadFactory createThreadFactory(final long stackSize) {       
        return new ThreadFactory(){
            final ThreadGroup g = Thread.currentThread().getThreadGroup();
            private final AtomicLong counter= new AtomicLong();
            {
                //take care of contextClassLoader and AccessControlContext              
            }

            @Override
            public Thread newThread(Runnable r) {               
                Thread t = new Thread(g, r, composeName(r), stackSize);
                return t;
            }

            protected String composeName(Runnable r) {
                return String.format("Regexp dedicated compiler: %d @ %tF %<tT ", counter.incrementAndGet(), System.currentTimeMillis());
            }   
        };
    };

    public Pattern compile(final String regex){//add flags if you need 'em
        Callable<Pattern> c = new Callable<Pattern>(){
            @Override
            public Pattern call() throws Exception {
                return Pattern.compile(regex);
            }           
        };

        try{
            Pattern p = svc.submit(c).get();
            return p;
        }catch(InterruptedException _ie){
            Thread.currentThread().interrupt();
            throw new IllegalStateException(_ie);
        } catch(CancellationException _cancel){
            throw new AssertionError(_cancel);//shan't happen
        } catch(ExecutionException _exec){
            Throwable t = _exec.getCause();
            if (t instanceof RuntimeException) throw (RuntimeException) t;
            if (t instanceof Error) throw (Error) t;
            throw new IllegalStateException(t==null?_exec:t);
        }


    }
}

If you are running out of stack consider creating a dedicate thread w/ enough stack especially for running the request. Sample code below.

package t1;

import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Pattern;

public class RegExpRunner {
    ExecutorService svc;    
    public RegExpRunner(long stackSize){
        init(stackSize);

    }


    void init(long stackSize){
        final SynchronousQueue<Runnable> queue = new SynchronousQueue<Runnable>();

        svc = new ThreadPoolExecutor(1, 2, 60, TimeUnit.SECONDS,  queue, createThreadFactory(stackSize), new RejectedExecutionHandler(){//wait if there is a concurrent compile and no available threads
            @Override
            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                try{
                    queue.put(r);
                }catch(InterruptedException _ie){
                    Thread.currentThread().interrupt();
                    throw new IllegalStateException(_ie);
                }
            }                   
        });
    }

    private ThreadFactory createThreadFactory(final long stackSize) {       
        return new ThreadFactory(){
            final ThreadGroup g = Thread.currentThread().getThreadGroup();
            private final AtomicLong counter= new AtomicLong();
            {
                //take care of contextClassLoader and AccessControlContext              
            }

            @Override
            public Thread newThread(Runnable r) {               
                Thread t = new Thread(g, r, composeName(r), stackSize);
                return t;
            }

            protected String composeName(Runnable r) {
                return String.format("Regexp dedicated compiler: %d @ %tF %<tT ", counter.incrementAndGet(), System.currentTimeMillis());
            }   
        };
    };

    public Pattern compile(final String regex){//add flags if you need 'em
        Callable<Pattern> c = new Callable<Pattern>(){
            @Override
            public Pattern call() throws Exception {
                return Pattern.compile(regex);
            }           
        };

        try{
            Pattern p = svc.submit(c).get();
            return p;
        }catch(InterruptedException _ie){
            Thread.currentThread().interrupt();
            throw new IllegalStateException(_ie);
        } catch(CancellationException _cancel){
            throw new AssertionError(_cancel);//shan't happen
        } catch(ExecutionException _exec){
            Throwable t = _exec.getCause();
            if (t instanceof RuntimeException) throw (RuntimeException) t;
            if (t instanceof Error) throw (Error) t;
            throw new IllegalStateException(t==null?_exec:t);
        }


    }
}
痕至 2024-10-27 01:32:42

我会尝试插入一些东西来装饰堆栈跟踪输出,类似于 ExceptionUtils 将重复调用分组到同一类或包裹。

I'd try plugging in something to decorate the stack trace output similar to ExceptionUtils to group repeated calls to the same Class or Package.

不醒的梦 2024-10-27 01:32:42

当我重现问题时,我会触发手动线程转储。最有可能的是,stackoverflow 仅在一段时间后才会抛出。因此,我们可以快速触发 jvm 上的线程转储,这将通过在堆栈溢出之前打印有问题线程的整个堆栈来为我们提供有关调用者的详细信息。

I would trigger a manual thread dump while I reproduce the issue. Most probably the stackoverflow is thrown only after some time. So, we can quickly trigger a Thread dump on jvm which will give us details about the caller by printing the entire stack of the problematic thread before its stack gets over flown.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文