Как получить полный стек StackOverflowError - PullRequest
29 голосов
/ 02 марта 2011

При наблюдении за 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 с.

Ответы [ 5 ]

37 голосов
/ 12 октября 2013

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

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

-XX:MaxJavaStackTraceDepth=1000000

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

Этот список нестандартных опций 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). </p>

Запуск образца вопроса с этим флагом дает следующий результат:

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

4 голосов
/ 02 марта 2011

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

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

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

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

1 голос
/ 02 марта 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);
        }


    }
}
0 голосов
/ 08 июня 2016

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

0 голосов
/ 02 марта 2011

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

...