Как получить полный стек StackOverflowError

Как получить полный стек вызовов при обнаружении ошибки 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]

Но я не вижу методов main и a в трассировке стека. Я предполагаю, что это из-за переполнения, самая новая запись в стеке заменяет самую старую (?).

Теперь, как получить записи стека a и main на выходе?

Фон в том, что я получаю StackOverflowError (но это не бесконечная рекурсия, потому что этого не происходит при увеличении размера стека), и трудно обнаружить проблему в коде. Я получаю только несколько строк из java.util.regex.Pattern, но не информацию о том, какой код вызвал это. Приложение слишком сложное, чтобы устанавливать точку останова при каждом вызове Patterns.


person Grzegorz Oledzki    schedule 02.03.2011    source источник
comment
Учитывая, что вы можете увеличить размер стека, и он исчезнет, ​​можете ли вы попробовать уменьшить размер стека и посмотреть, позволит ли это вам увидеть больше трассировки стека? Не могу вспомнить, как вы контролируете размер стека в JVM и разрешено ли вам делать его достаточно маленьким, но это может помочь в диагностике проблемы.   -  person Tom Quarendon    schedule 02.03.2011
comment
@ Том, размер стека контролируется новым Thread(ThreadGroup group, Runnable target, String name, long stackSize).   -  person bestsss    schedule 02.03.2011
comment
@Grzegorz, если функция I использует рекурсию, а у вас заканчивается стек. Просто увеличьте его. Следуйте идее из предыдущего комментария и запустите код в отдельном потоке с достаточно большим стеком. Вы гарантируете отсутствие загрязненного (маленького) стека, и все в порядке. Сохраняйте поток, чтобы он не создавался заново при каждом вызове.   -  person bestsss    schedule 02.03.2011
comment
@ Том, это помогает с искусственным примером. Позвольте мне попробовать в реальном приложении. Я ожидаю, что я получу поток таких сообщений, все еще хороший момент.   -  person Grzegorz Oledzki    schedule 02.03.2011
comment
@bestsss Я представлял, что это делается с помощью аргумента -X‹something› JVM, а не модификации кода. Если это приложение с одним потоком, не будет никаких конструкторов Thread().   -  person Tom Quarendon    schedule 02.03.2011
comment
@ Том, -Xss - это то, что тебе нужно, но обычно меня не интересуют однопоточные приложения, кроме некоторых микробенчмарков.   -  person bestsss    schedule 02.03.2011
comment
@bestsss: Наверное, каждому свое :-)   -  person Tom Quarendon    schedule 02.03.2011
comment
+1 только за заголовок! :D я просто вижу, как близкие тролли пытаются найти причину, чтобы закрыть это!! но это связано с переполнением стека ... не могу попросить его перейти на другой форум LOL   -  person necromancer    schedule 11.10.2013


Ответы (5)


JVM имеет искусственное ограничение в 1024 записи, которые вы можете иметь в трассировке стека для исключения или ошибки, вероятно, для экономии памяти при ее возникновении (поскольку виртуальная машина должна выделять память для хранения трассировки стека).

К счастью, есть флаг, позволяющий увеличить этот лимит. Просто запустите свою программу со следующим аргументом:

-XX:MaxJavaStackTraceDepth=1000000

Это напечатает до 1 миллиона записей вашей трассировки стека, чего должно быть более чем достаточно. Также можно установить это значение на 0, чтобы установить неограниченное количество записей.

Этот список стандартные параметры JVM содержат более подробную информацию:

Максимум. нет. строк в трассировке стека для исключений Java (0 означает все). В Java > 1.6 значение 0 действительно означает 0. Для печати всего стека необходимо указать значение -1 или любое отрицательное число (проверено с 1.6.0_22, 1.7.0 в Windows). В 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 строки в нижней части трассировки стека, что не является полной трассировкой стека, но, вероятно, является наиболее полезной ее частью.

person Cyrille Ka    schedule 12.10.2013
comment
Примечание. Начиная с Java 9 -1 больше не является правильным значением для MaxJavaStackTraceDepth. - person turbanoff; 21.03.2019

Насколько я знаю, невозможно получить полную трассировку стека (правда, я не знаю, почему).

Однако то, что вы можете сделать, чтобы отследить проблему, — это вручную проверить глубину стека в вашем затронутом коде следующим образом:

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

Таким образом, вы можете найти свой собственный код в нижней части стека, когда вы нажмете точку останова.

person Joachim Sauer    schedule 02.03.2011
comment
Насколько я знаю, невозможно получить полную трассировку стека (однако я действительно не знаю, почему). Одна очевидная причина того, что вам может не хватить памяти даже для попытки собрать ее и восстановить его. Трассировка стека не требуется самой java (безопасна по соображениям безопасности). 300, как правило, довольно низкая глубина (если только в стеке нет тонн переменных), но сбор такой трассировки стека не бесплатен. - person bestsss; 02.03.2011

Если у вас заканчивается стек, рассмотрите возможность создания выделенного потока с достаточным стеком, особенно для выполнения запроса. Пример кода ниже.

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


    }
}
person Community    schedule 02.03.2011

Я бы попробовал подключить что-нибудь для украшения вывода трассировки стека, похожее на ExceptionUtils, чтобы сгруппировать повторные вызовы одного и того же класса или Упаковка.

person David O'Meara    schedule 02.03.2011
comment
Я думаю, дело в том, что трассировка стека, о которой сообщает java, останавливается перед нижней частью (т.е. основной), поэтому информации для отчета нет. Предположительно, это максимальный объем элементов, о которых он сообщает, и когда происходит переполнение стека, программа выходит за этот предел. Что немного странно, так это то, что эта емкость не совпадает с глубиной, при которой происходит переполнение стека. - person Tom Quarendon; 02.03.2011

Я бы запускал ручной дамп потока, пока я воспроизводил проблему. Скорее всего, stackoverflow выбрасывается только через некоторое время. Таким образом, мы можем быстро вызвать дамп потока на jvm, который даст нам подробную информацию о вызывающем объекте, распечатав весь стек проблемного потока до того, как его стек переполнится.

person anony    schedule 08.06.2016