Подтвердить что ты не робот

Как получить полный стек 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, но не информацию, которую этот код назвал. Приложение слишком сложно установить точку останова при каждом вызове Pattern s.

4b9b3361

Ответ 1

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 строки в нижней части трассировки стека, которая не является полной трассировкой стека, но, вероятно, является самой полезной ее частью.

Ответ 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 нужно будет найти путем экспериментов (достаточно высоко, чтобы не срабатывать в "хороших" ситуациях и достаточно низка, чтобы не быть недоступными). Конечно, это замедлит ваш код и должно использоваться только для отладки проблемы.

Обновление: похоже, я пропустил, что проблема возникает в Pattern, что усложняет ситуацию. Тем не менее, вы можете использовать контрольную точку условного метода в одном из методов Pattern в трассировке стека с таким условием (фактическое значение может потребоваться для настройки):

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

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

Ответ 3

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

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


    }
}

Ответ 4

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

Ответ 5

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