Вопрос по java – Отслеживание проблемы утечки памяти / сбора мусора в Java

79

Это проблема, которую я пытался отследить уже пару месяцев. У меня работает приложение Java, которое обрабатывает потоки XML и сохраняет результат в базе данных. Были периодические проблемы с ресурсами, которые очень трудно отследить.

Background: На производственной коробке (где проблема наиболее заметна) у меня нет особенно хорошего доступа к коробке, и я не смог запустить Jprofiler. Этот блок представляет собой 64-битный четырехъядерный компьютер, 8 Гб, работающий с Centos 5.2, tomcat6 и java 1.6.0.11. Начинается с этих java-опций

JAVA_OPTS="-server -Xmx5g -Xms4g -Xss256k -XX:MaxPermSize=256m -XX:+PrintGCDetails -
XX:+PrintGCTimeStamps -XX:+UseConcMarkSweepGC -XX:+PrintTenuringDistribution -XX:+UseParNewGC"

Технологический стек выглядит следующим образом:

Centos 64-bit 5.2 Java 6u11 Tomcat 6 Spring/WebMVC 2.5 Hibernate 3 Quartz 1.6.1 DBCP 1.2.1 Mysql 5.0.45 Ehcache 1.5.0 (and of course a host of other dependencies, notably the jakarta-commons libraries)

Наиболее близким к воспроизведению проблемы является 32-разрядный компьютер с более низкими требованиями к памяти. Это я контролирую. Я исследовал его до смерти с помощью JProfiler и исправил многие проблемы с производительностью (проблемы с синхронизацией, предварительная компиляция / кэширование запросов xpath, уменьшение пула потоков и удаление ненужных предварительных выборок из спящего режима и чрезмерное усердие «согревание кэша» во время обработки).

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

The Problem: JVM, похоже, полностью игнорирует настройки использования памяти, заполняет всю память и перестает отвечать на запросы. Это проблема для конечного клиента, который ожидает регулярного опроса (5-минутный базис и 1-минутная повторная попытка), а также для наших рабочих групп, которые постоянно уведомляются о том, что ящик перестал отвечать, и должны перезапустить его. На этой коробке больше ничего не работает.

Эта проблемаappears быть сборщиком мусора. Мы используем коллектор ConcurrentMarkSweep (как отмечено выше), поскольку оригинальный коллектор STW вызывал тайм-ауты JDBC и становился все более медленным. Журналы показывают, что по мере увеличения использования памяти он начинает генерировать сбои cms и возвращает к исходному сборщику остановок, который затем, похоже, не собирает должным образом.

Однако при работе с jprofiler & quot; Запустить GC & quot; Кнопка, кажется, хорошо очищает память, а не показывает увеличивающуюся площадь, но так как я не могу подключить jprofiler напрямую к производственной коробке, и разрешение проверенных горячих точек, кажется, не работает, я остался с вуду настройки слепой сборки мусора.

What I have tried:

Profiling and fixing hotspots. Using STW, Parallel and CMS garbage collectors. Running with min/max heap sizes at 1/2,2/4,4/5,6/6 increments. Running with permgen space in 256M increments up to 1Gb. Many combinations of the above. I have also consulted the JVM [tuning reference](http://java.sun.com/javase/technologies/hotspot/gc/gc_tuning_6.html) , but can't really find anything explaining this behavior or any examples of _which_ tuning parameters to use in a situation like this. I have also (unsuccessfully) tried jprofiler in offline mode, connecting with jconsole, visualvm, but I can't seem to find anything that will interperet my gc log data.

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

Может ли кто-нибудь дать какой-нибудь совет относительно:
а) Почему JVM использует 8 физических гигабайт и 2 ГБ пространства подкачки, когда она настроена на максимальное значение менее 6.
б) Ссылка на настройку ГХ, которая фактически объясняет или дает разумные примеры того, когда и с какими настройками использовать расширенные коллекции.
в) Ссылка на наиболее распространенные утечки памяти Java (я понимаю невостребованные ссылки, но я имею в виду на уровне библиотеки / фреймворка, или что-то более инеренет в структурах данных, таких как хэш-карты).

Спасибо за любую информацию, которую вы можете предоставить.

EDIT
Эмиль Н:
1) Да, мой кластер разработки - это зеркало производственных данных, вплоть до медиасервера. Основное различие заключается в 32/64-битном и объеме доступной оперативной памяти, которую я не могу очень легко воспроизвести, но код, запросы и настройки идентичны.

2) Существует некоторый устаревший код, основанный на JaxB, но при переупорядочении заданий, чтобы избежать конфликтов планирования, я обычно исключаю это выполнение, поскольку оно выполняется один раз в день. Основной синтаксический анализатор использует запросы XPath, которые обращаются к пакету java.xml.xpath. Это было источником нескольких горячих точек, для одного запросы не были предварительно скомпилированы, а две ссылки на них были в жестко закодированных строках. Я создал потокобезопасный кеш (hashmap) и факторизовал ссылки на запросы xpath в качестве окончательных статических строк, что значительно снизило потребление ресурсов. Запросы по-прежнему являются большой частью обработки, но это должно быть потому, что это главная ответственность приложения.

3) Дополнительное примечание, другим основным потребителем являются операции с изображениями из JAI (повторная обработка изображений из канала). Я не знаком с графическими библиотеками Java, но из того, что я обнаружил, они не особенно утечки.

(спасибо за ответы, ребята!)

UPDATE:
Мне удалось подключиться к производственному экземпляру с помощью VisualVM, но он отключил опцию GC visualization / run-GC (хотя я мог просматривать его локально). Интересная вещь: выделение кучи виртуальной машины подчиняется JAVA_OPTS, а фактическая выделенная куча удобно расположена на уровне 1-1,5 гигабайта и, похоже, не протекает, но мониторинг на уровне блока все еще показывает схему утечки, но это не отражается в мониторинге ВМ. На этой коробке больше ничего не работает, поэтому я в тупике.

Кроме того, какой XML-парсер вы используете? Emil H
Используете ли вы данные реального мира и базу данных реального мира для тестирования? Предпочтительно копия производственных данных? Emil H
Вы смотрели количество выделенных байтовых буферов и кто их выделяет? Sean McCauliff
+1 - это один из лучших вопросов, которые я когда-либо читал. Я хотел бы иметь больше, чтобы предложить с точки зрения помощи. Я вернусь к этому, чтобы узнать, есть ли у кого-нибудь что-нибудь умное, чтобы сказать. duffymo

Ваш Ответ

7   ответов
90

я наконец-то нашел проблему, которая вызывала это, и я публикую подробный ответ на случай, если у кого-то еще возникнут эти проблемы.

Я пробовал jmap во время процесса, но это обычно приводило к зависанию jvm, и мне пришлось бы запускать его с --force. Это привело к созданию дампов кучи, в которых, по-видимому, отсутствовало много данных или, по крайней мере, отсутствовали ссылки между ними. Для анализа я попробовал jhat, который представляет много данных, но не так, как их интерпретировать. Во-вторых, я попробовал инструмент анализа памяти на основе затмения (http://www.eclipse.org/mat/ ), который показал, что куча в основном классов, связанных с tomcat.

Проблема заключалась в том, что jmap не сообщал о реальном состоянии приложения, а только перехватывал классы при завершении работы, в основном это были классы tomcat.

Я попробовал еще несколько раз, и заметил, что было несколько очень больших объектов модели (фактически в 2-3 раза больше, чем было отмечено в базе данных как общедоступное).

Используя это, я проанализировал медленные журналы запросов и несколько проблем, связанных с производительностью. Я попробовал очень ленивую загрузку (http://docs.jboss.org/hibernate/core/3.3/reference/en/html/performance.html ), а также заменив несколько операций гибернации на прямые запросы jdbc (в основном там, где он имел дело с загрузкой и работой с большими коллекциями - замены jdbc просто работали непосредственно с таблицами объединения), и заменил некоторые другие неэффективные запросы, которые выполнял mysql. протоколирование.

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

Наконец, я нашел параметр: -XX: + HeapDumpOnOutOfMemoryError. В результате получился очень большой (~ 6,5 ГБ) файл hprof, который точно отображал состояние приложения. По иронии судьбы, файл был настолько большим, что он не мог его проанализировать даже на коробке с 16 ГБ оперативной памяти. К счастью, MAT удалось создать несколько симпатичных графиков и показать более качественные данные.

На этот раз выделялась одна кварцевая нить, которая занимала 4,5 ГБ из 6 ГБ кучи, и большая часть этого была в спящем состоянии StatefulPersistenceContext (https://www.hibernate.org/hib_docs/v3/api/org/hibernate/engine/StatefulPersistenceContext.html ). Этот класс используется внутренним hibernate в качестве основного кеша (я отключил второй уровень и кеш запросов при поддержке EHCache).

Этот класс используется для включения большинства функций hibernate, поэтому его нельзя напрямую отключить (вы можете обойти его напрямую, но Spring не поддерживает сеанс без сохранения состояния), и я был бы очень удивлен, если бы у него был такой серьезная утечка памяти в зрелом продукте. Итак, почему это протекало сейчас?

, это была комбинация вещей: Кварцевый пул потоков создается с определенными вещами, такими как threadLocal, а Spring внедряет фабрику сессий, которая создает сеанс в начале жизненного цикла кварцевых нитей, который затем повторно используется для запуска различных кварцевых заданий, использующих сеанс гибернации. Затем Hibernate кэшировал в сеансе, что является его ожидаемым поведением.

Тогда проблема заключается в том, что пул потоков никогда не освобождает сеанс, поэтому hibernate оставался резидентным и поддерживал кэш в течение жизненного цикла сеанса. Так как при этом использовалась поддержка шаблона hibernate для весен, явного использования сессий не было (мы используем иерархию dao -> gt; driver -> quartz-job), дао вводится с помощью hibernate через конфиги через Spring, поэтому операции выполняются непосредственно на шаблонах).

Таким образом, сеанс никогда не закрывался, hibernate поддерживал ссылки на объекты кэша, поэтому они никогда не собирались мусором, поэтому каждый раз, когда запускалось новое задание, он просто продолжал заполнять кэш локально для потока, поэтому даже не было любое разделение между различными работами. Кроме того, поскольку это работа с интенсивной записью (очень мало чтения), кеш в основном тратится впустую, поэтому объекты продолжают создаваться.

Решение: создайте метод dao, который явно вызывает session.flush () и session.clear (), и вызывайте этот метод в начале каждого задания.

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

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

Error: User Rate Limit Exceededdocs.jboss.org/hibernate/orm/4.3/javadocs/org/hibernate/engine/…Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded@Transactional(propagation = Propagation.NOT_SUPPORTED)Error: User Rate Limit ExceededPropagation.REQUIREDError: User Rate Limit Exceeded
4

что память, кроме кучи, течет, вы упоминаете, что куча остается стабильной. Классическим кандидатом является permgen (постоянная генерация), который состоит из двух вещей: загруженные объекты класса и интернированные строки. Так как вы сообщаете о подключении к VisualVM, вы сможете увидеть количество загруженных классов, если будет продолжаться увеличениеloaded классы (важно, что visualvm также показывает общее количество когда-либо загруженных классов, это нормально, если это возрастет, но количество загруженных классов должно стабилизироваться через определенное время).

Если это оказывается утечкой permgen, то отладка становится сложнее, так как инструменты для анализа permgen скорее отсутствуют по сравнению с кучей. Лучше всего запустить на сервере небольшой сценарий, который периодически (каждый час?) Вызывает:

jmap -permstat <pid> > somefile<timestamp>.txt

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

Как только вы определили определенные классы как загруженные и не выгруженные, вы можете мысленно выяснить, где они могут быть сгенерированы, в противном случае вы можете использовать jhat для анализа дампов, созданных с помощью jmap -dump. Я оставлю это для будущих обновлений, если вам понадобится информация.

Error: User Rate Limit Exceeded liam
Error: User Rate Limit Exceeded liam
1

Unfortunately, the problem also pops up sporadically, it seems to be unpredictable, it can run for days or even a week without having any problems, or it can fail 40 times in a day, and the only thing I can seem to catch consistently is that garbage collection is acting up.

Похоже, это связано с вариантом использования, который выполняется до 40 раз в день, а затем уже не в течение нескольких дней. Надеюсь, вы не просто отслеживаете только симптомы. Это должно быть чем-то, что вы можете сузить, отслеживая действия действующих лиц приложения (пользователей, рабочих мест, служб).

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

2

Из Javadoc.

A direct byte buffer may be created by invoking the allocateDirect factory method of this class. The buffers returned by this method typically have somewhat higher allocation and deallocation costs than non-direct buffers. The contents of direct buffers may reside outside of the normal garbage-collected heap, and so their impact upon the memory footprint of an application might not be obvious. It is therefore recommended that direct buffers be allocated primarily for large, long-lived buffers that are subject to the underlying system's native I/O operations. In general it is best to allocate direct buffers only when they yield a measureable gain in program performance.

Возможно, код Tomcat использует это для ввода / вывода; настройте Tomcat для использования другого соединителя.

В противном случае у вас может быть поток, который периодически выполняет System.gc (). & Quot; -XX: + ExplicitGCInvokesConcurrent & Quot; может быть интересным вариантом, чтобы попробовать.

Error: User Rate Limit Exceededstackoverflow.com/questions/26041117/…Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded liam
Error: User Rate Limit Exceeded
1

Кроме того, я считаю, чтоvisualgcВ настоящее время поставляется с JDK 6, это отличный способ увидеть, что происходит в памяти. Он прекрасно показывает районы Эдема, Поколения и Перми и переходное поведение ГХ. Все, что вам нужно, это PID процесса. Может быть, это поможет, пока вы работаете над JProfile.

А как насчет аспектов трассировки / ведения журнала Spring? Может быть, вы можете написать простой аспект, применить его декларативно и таким образом сделать профилировщик для бедняков.

Error: User Rate Limit Exceeded liam
4

-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=<port>
...

Мониторинг и управление с помощью JMX

А затем присоедините с помощью JConsole,VisualVM?

Это нормально, чтобы сделать дамп кучи сjmap?

Если да, то вы можете проанализировать дамп кучи на наличие утечек с помощью JProfiler (у вас уже есть),jhatVisualVM,Eclipse MAT, Также сравните дампы кучи, которые могут помочь найти утечки / шаблоны.

И, как вы упомянули, Джакарта. При использовании jakarta-commons-logging возникает проблема, связанная с удержанием загрузчика классов. Для хорошего чтения на этом чеке

Один день из жизни охотника за утечками памяти (release(Classloader))

Error: User Rate Limit Exceeded liam
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded liam
Error: User Rate Limit Exceeded liam
1

Моя технология заключается в следующем:

Граальс 2.2.4

tomcat7

кварц-плагин 1.0

Я использую два источника данных в своем приложении. Это определитель специфичности к ошибочным причинам ..

Еще одна вещь, которую следует учитывать, это то, что кварцевый плагин вставляет сеанс гибернации в кварцевые нити, как говорит @liam, а кварцевые нити еще живы, пока я не закончу приложение.

Моей проблемой была ошибка в ORM Grails в сочетании с тем, как плагин обрабатывал сессию, и двумя моими источниками данных.

Кварцевый плагин имел слушателя для инициации и уничтожения спящих сессий

public class SessionBinderJobListener extends JobListenerSupport {

    public static final String NAME = "sessionBinderListener";

    private PersistenceContextInterceptor persistenceInterceptor;

    public String getName() {
        return NAME;
    }

    public PersistenceContextInterceptor getPersistenceInterceptor() {
        return persistenceInterceptor;
    }

    public void setPersistenceInterceptor(PersistenceContextInterceptor persistenceInterceptor) {
        this.persistenceInterceptor = persistenceInterceptor;
    }

    public void jobToBeExecuted(JobExecutionContext context) {
        if (persistenceInterceptor != null) {
            persistenceInterceptor.init();
        }
    }

    public void jobWasExecuted(JobExecutionContext context, JobExecutionException exception) {
        if (persistenceInterceptor != null) {
            persistenceInterceptor.flush();
            persistenceInterceptor.destroy();
        }
    }
}

В моем случае,persistenceInterceptor экземплярыAggregatePersistenceContextInterceptorи у него был списокHibernatePersistenceContextInterceptor, Один для каждого источника данных.

Каждая операция связана сAggregatePersistenceContextInterceptor оно передано HibernatePersistence, без каких-либо изменений или обработок.

Когда мы звонимinit() наHibernatePersistenceContextInterceptor он увеличивает статическую переменную ниже

private static ThreadLocal<Integer> nestingCount = new ThreadLocal<Integer>();

Я не знаю, какой будет этот статический счет. Я просто знаю, что он увеличился в два раза, по одному на источник данных, из-заAggregatePersistence реализация.

До тех пор, пока я не объясню синарио.

Проблема приходит сейчас ...

Когда моя кварцевая работа заканчивается, плагин вызывает слушателя для сброса и уничтожения спящих сессий, как вы можете видеть в исходном кодеSessionBinderJobListener.

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

Упрощение того, что сделал Hibernate:

if(--nestingCount.getValue() > 0)
    do nothing;
else
    close the session;

Это основа моей утечки памяти ... Кварцевые потоки все еще живут со всеми объектами, используемыми в сеансе, потому что Grails ORM не закрывают сеанс из-за ошибки, вызванной тем, что у меня есть два источника данных.

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

Похожие вопросы