Невозможно отследить источник ошибки переполнения стека

#java #stack-overflow

#java #переполнение стека

Вопрос:

У меня есть приложение Java, которое работает как служба. После нескольких дней работы я получаю ошибку переполнения стека. Проблема в том, что фрейм стека в моем источнике, в котором возникает ошибка, отсутствует в ошибке, о которой сообщает функция printStackTrace. Я понятия не имею, как найти, в чем проблема.

Это результат printStackTrace:

 java.lang.StackOverflowError
    at java.util.LinkedList.listIterator(LinkedList.java:684)
    at java.util.SubList$1.<init>(AbstractList.java:700)
    at java.util.SubList.listIterator(AbstractList.java:699)
    at java.util.SubList$1.<init>(AbstractList.java:700)
    at java.util.SubList.listIterator(AbstractList.java:699)
    at java.util.SubList$1.<init>(AbstractList.java:700)
    at java.util.SubList.listIterator(AbstractList.java:699)
        ... (this is repeated hundreds of times)
    at java.util.SubList.listIterator(AbstractList.java:699)
    at java.util.SubList$1.<init>(AbstractList.java:700)
    at java.util.SubList.listIterator(AbstractList.java:699)
    at java.util.SubList$1.<init>(AbstractList.java:700)
  

Комментарии:

1. Происходит ли это последовательно? Вы можете подключиться к нему с помощью профилировщика и найти горячие точки кода. Или, что еще лучше, настройте агент для выполнения дампа потока при ошибке. Я знаю, что это можно сделать автоматически при ошибках OutOfMemory, не уверен в других типах. Но это стоит изучить.

2. Некоторые рекурсивные алгоритмы, такие как рекурсивное обнаружение больших двоичных объектов, выигрывают от увеличения размера стека.

Ответ №1:

Ваша проблема может быть воспроизведена следующим образом:

 List<String> l = ...;

for (int i = 0; i < 2800; i  ) {
    l = l.subList(...);
}
l.listIterator(...);
  

Итак, обратите внимание на все вызовы subList() и убедитесь, что они не образуют длинную цепочку подсписков, как указано выше.

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

Комментарии:

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

2.Также: интересно, что SubList.subList() не создается подсписок на основе оригинала List . На мой взгляд, это была бы хорошая оптимизация.

3. Да, возможно, это причина. Я использовал подсписок для поворота журнала производительности: сначала я удалил записи с помощью подсписка, а затем добавил новые записи в конец. Это повторялось тысячи раз, поэтому … однако вопрос остается без ответа: как найти точку в моем источнике, если она глубоко спрятана в стеке, и printStackTrace не может ее достичь … может быть, мне стоит попробовать комментарий @G_H.

Ответ №2:

Ищите варианты использования List.subList() метода — это единственное место, где вы можете получить экземпляр java.util.SubList (класс с видимостью пакета).

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