Вопрос

У меня действительно странная проблема с приложением Java.

По сути, это веб-страница, использующая Magnolia (систему CMS), в производственной среде доступно 4 экземпляра.Иногда процессор достигает 100% в Java-процессе.

Итак, первый подход заключался в том, чтобы сделать дамп потока и проверить поток-нарушитель, и я обнаружил странность:

"GC task thread#0 (ParallelGC)" prio=10 tid=0x000000000ce37800 nid=0x7dcb runnable 
"GC task thread#1 (ParallelGC)" prio=10 tid=0x000000000ce39000 nid=0x7dcc runnable 

Хорошо, это довольно странно, у меня никогда не было подобных проблем со сборщиком мусора, поэтому следующее, что мы сделали, это активировали JMX и с помощью jvisualvm проверили машину:использование кучи памяти было очень высоким (95%).

Наивный подход:Увеличьте память, поэтому проблема появится позже, в результате на перезапущенном сервере с увеличенной памятью (6 ГБ!) проблема появилась через 20 часов после перезапуска, а на других серверах с меньшим объемом памяти (4 ГБ!), которые работали 10 часов. дней, проблема появилась еще через несколько дней.Кроме того, я попытался использовать журнал доступа Apache с неудачного сервера и использовать JMeter для воспроизведения запросов на локальный сервер в попытке воспроизвести ошибку...это тоже не сработало.

Затем я еще немного изучил журналы, чтобы найти эти ошибки.

info.magnolia.module.data.importer.ImportException: Error while importing with handler [brightcoveplaylist]:GC overhead limit exceeded
at info.magnolia.module.data.importer.ImportHandler.execute(ImportHandler.java:464)
at info.magnolia.module.data.commands.ImportCommand.execute(ImportCommand.java:83)
at info.magnolia.commands.MgnlCommand.executePooledOrSynchronized(MgnlCommand.java:174)
at info.magnolia.commands.MgnlCommand.execute(MgnlCommand.java:161)
at info.magnolia.module.scheduler.CommandJob.execute(CommandJob.java:91)
at org.quartz.core.JobRunShell.run(JobRunShell.java:216)
at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:549)
    Caused by: java.lang.OutOfMemoryError: GC overhead limit exceeded

Другой пример

    Caused by: java.lang.OutOfMemoryError: GC overhead limit exceeded
    at java.util.Arrays.copyOf(Arrays.java:2894)
    at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:117)
    at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:407)
    at java.lang.StringBuilder.append(StringBuilder.java:136)
    at java.lang.StackTraceElement.toString(StackTraceElement.java:175)
    at java.lang.String.valueOf(String.java:2838)
    at java.lang.StringBuilder.append(StringBuilder.java:132)
    at java.lang.Throwable.printStackTrace(Throwable.java:529)
    at org.apache.log4j.DefaultThrowableRenderer.render(DefaultThrowableRenderer.java:60)
    at org.apache.log4j.spi.ThrowableInformation.getThrowableStrRep(ThrowableInformation.java:87)
    at org.apache.log4j.spi.LoggingEvent.getThrowableStrRep(LoggingEvent.java:413)
    at org.apache.log4j.AsyncAppender.append(AsyncAppender.java:162)
    at org.apache.log4j.AppenderSkeleton.doAppend(AppenderSkeleton.java:251)
    at org.apache.log4j.helpers.AppenderAttachableImpl.appendLoopOnAppenders(AppenderAttachableImpl.java:66)
    at org.apache.log4j.Category.callAppenders(Category.java:206)
    at org.apache.log4j.Category.forcedLog(Category.java:391)
    at org.apache.log4j.Category.log(Category.java:856)
    at org.slf4j.impl.Log4jLoggerAdapter.error(Log4jLoggerAdapter.java:576)
    at info.magnolia.module.templatingkit.functions.STKTemplatingFunctions.getReferencedContent(STKTemplatingFunctions.java:417)
    at info.magnolia.module.templatingkit.templates.components.InternalLinkModel.getLinkNode(InternalLinkModel.java:90)
    at info.magnolia.module.templatingkit.templates.components.InternalLinkModel.getLink(InternalLinkModel.java:66)
    at sun.reflect.GeneratedMethodAccessor174.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:622)
    at freemarker.ext.beans.BeansWrapper.invokeMethod(BeansWrapper.java:866)
    at freemarker.ext.beans.BeanModel.invokeThroughDescriptor(BeanModel.java:277)
    at freemarker.ext.beans.BeanModel.get(BeanModel.java:184)
    at freemarker.core.Dot._getAsTemplateModel(Dot.java:76)
    at freemarker.core.Expression.getAsTemplateModel(Expression.java:89)
    at freemarker.core.BuiltIn$existsBI._getAsTemplateModel(BuiltIn.java:709)
    at freemarker.core.BuiltIn$existsBI.isTrue(BuiltIn.java:720)
    at freemarker.core.OrExpression.isTrue(OrExpression.java:68)

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

Итак, это проблема с ПАМЯТЬЮ, которая проявляется в ЦП, поэтому, если проблема с использованием памяти решена, то с ЦП все должно быть в порядке, поэтому я взял дамп динамической памяти, к сожалению, он был слишком велик, чтобы его открыть ( файл был 10ГБ), все равно я запускал сервер локально, немного загрузил его и снял heapdump, открыв его, я обнаружил кое-что интересное:

Есть ТОННА примеров

AbstractReferenceMap$WeakRef  ==> Takes 21.6% of the memory, 9 million instances
AbstractReferenceMap$ReferenceEntry  ==> Takes 9.6% of the memory, 3 million instances

Кроме того, я нашел карту, которая, похоже, используется в качестве «кэша» (ужасно, но это правда), проблема в том, что такая карта НЕ синхронизируется и распределяется между потоками (будучи статической), проблема может быть не только одновременная запись, но также и тот факт, что из-за отсутствия синхронизации нет гарантии, что поток A увидит изменения, внесенные в карту потоком B, однако я не могу понять, как связать эту подозрительную карту с помощью анализатора затмений памяти , поскольку он не использует AbstracReferenceMap, это обычный HashMap.

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

Проблемы для меня

  1. не могу воспроизвести ошибку
  2. Я не могу понять, куда, черт возьми, утекает память (если это так)

Есть вообще идеи?

Это было полезно?

Решение

"Безоперационный" finalize() методы определенно следует удалить, поскольку они, вероятно, усугубят любые проблемы с производительностью GC.Но я подозреваю, что у вас есть и другие проблемы с утечкой памяти.

Совет:

  • Сначала избавьтесь от бесполезного finalize() методы.

  • Если у вас есть другие finalize() методы, подумайте о том, чтобы избавиться от них.(В зависимости от завершения делать что-то, как правило, плохая идея ...)

  • Используйте профилировщик памяти, чтобы попытаться определить объекты, которые подвергаются утечке, и что является причиной утечки.Есть много вопросов SO ...и другие ресурсы по поиску утечек в Java-коде.Например:


Теперь перейдем к вашим конкретным симптомам.

Прежде всего, место, где OutOfMemoryErrors были выброшены, вероятно, неуместно.

Однако тот факт, что у вас есть огромное количество AbstractReferenceMap$WeakRef и AbstractReferenceMap$ReferenceEntry объекты - это строка , указывающая на то , что что - то в вашем приложении или библиотеках , которые оно использует , выполняет огромное количество кэширования ...и что это кэширование связано с проблемой.(Тот самый AbstractReferenceMap класс является частью библиотеки Apache Commons Collections.Это суперкласс ReferenceMap и ReferenceIdentityMap.)

Вам нужно отследить объект карты (или объекты), которые эти WeakRef и ReferenceEntry объекты, которым принадлежат, и (целевые) объекты, на которые они ссылаются.Затем вам нужно выяснить, что создает это / их, и выяснить, почему записи не очищаются в ответ на высокий спрос на память.

  • Есть ли у вас надежные ссылки на целевые объекты в другом месте (что предотвратило бы нарушение WeakRefs)?

  • Используется ли карта (ы) неправильно, что может привести к утечке.(Внимательно прочитайте javadocs ...)

  • Используются ли карты несколькими потоками без внешней синхронизации?Это может привести к повреждению, которое потенциально может проявиться в виде масштабной утечки хранилища.


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


Наконец, ваше замечание о том, что проблема усугубляется, когда куча становится больше.На мой взгляд, это все еще согласуется с Reference / проблема, связанная с кэшем.

  • Reference объекты - это больше работы для GC, чем обычные ссылки.

  • Когда GC нужно "сломать" a Reference, это создает больше работы;например ,обработка справочных очередей.

  • Даже когда это происходит, результирующие недоступные объекты все равно не могут быть собраны как минимум до следующего цикла GC.

Таким образом , я могу видеть , как куча объемом 6 ГБ , заполненная ссылками , значительно увеличила бы процент времени , затрачиваемого на GC ...по сравнению с кучей объемом 4 ГБ, и это может привести к тому, что механизм "Ограничения накладных расходов GC" сработает раньше.

Но я считаю, что это скорее побочный симптом, чем первопричина.

Другие советы

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

После того, как вы сможете воспроизвести его, попробуйте уменьшить размер кучи, чтобы посмотреть, сможете ли вы воспроизвести его быстрее.Но сделайте это на секунду, поскольку небольшая куча может не достичь «предела накладных расходов сборщика мусора», что означает, что сборщик мусора тратит слишком много времени (по некоторым показателям 98%), пытаясь восстановить память.

В случае утечки памяти вам необходимо выяснить, в каком месте кода накапливаются ссылки на объекты.Например.он строит карту всех входящих сетевых запросов?Веб-поиск https://www.google.com/search?q=how+to+debug+java+memory+leaks показывает множество полезных статей о том, как отлаживать утечки памяти Java, включая советы по использованию таких инструментов, как Анализатор памяти Eclipse что вы используете.Поиск конкретного сообщения об ошибке https://www.google.com/search?q=GC+overhead+limit+exceeded это тоже полезно.

Нет операции finalize() методы не должны вызывать эту проблему, но вполне могут ее усугубить.Документ на финализировать() показывает, что наличие finalize() метод заставляет GC дважды определить, что на экземпляр нет ссылки (до и после вызова finalize()).

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

Важно, что их много AbstractReferenceMap$WeakRef экземпляры в памяти.Точка слабая ссылка заключается в обращении к объекту, не заставляя его оставаться в памяти. АннотацияСправочникКарта это карта, которая позволяет сделать ключи и/или значения слабыми или мягкими ссылками.(Точка мягкая ссылка состоит в том, чтобы попытаться сохранить объект в памяти, но позволить сборщику мусора освободить его, когда памяти становится мало.) В любом случае, все эти экземпляры WeakRef в памяти, вероятно, усугубляют проблему, но не должны хранить в памяти указанные ключи/значения карты.Что они имеют в виду?Что еще относится к этим объектам?

Попробуйте инструмент, который накладывает утечки в исходном коде, например, Plumbr

Есть ряд возможностей, возможно, некоторые из которых вы исследовали.

Это определенно утечка памяти какая-то.

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

Если у вас есть одна или несколько карт, что ваша программа генерирует, и вы не очищаете карту старых / ненужных записей, вы снова можете получить накопление использованной памяти. Например, я когда-то рассмотрел добавление карты, чтобы отслеживать технологические потоки, чтобы пользователь мог получить информацию из каждого потока, пока мой босс не указал, что в точке не были удалены нити, которые не удаляются с карты, поэтому, если пользователь остался вошли В и активном, они бы держали эти нити навсегда.

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

Удачи, проблемы памяти - это боль, чтобы отслеживать.

Вы говорите, что вы уже попробовали JVisualVM, чтобы осмотреть машину.Может быть, попробуйте снова, как это:

    .
  • На этот раз посмотрите на вкладку «Sampler -> Memory».

  • Он должен сказать вам, какие (типы) объекты занимают наибольшую память.

  • Затем узнайте, где такие объекты обычно создаются и удалены.

    .
  • много раз «странные» ошибки могут быть вызваны Java агенты подключены к JVM. Если у вас есть какие-либо агенты (например, jrebel / liverebel, newrelic, jprofiler), попробуйте работать без них.
  • Странные вещи также могут произойти при запуске JVM с нестандартными параметрами (-xx); Известно, что некоторые комбинации вызывают проблемы; Какие параметры вы используете в настоящее время?
  • Утечка памяти также может быть в самой магнолии, вы пытались гугламить «утечка магнолии»? Вы используете любые 3-й партии модулей магнолии? Если возможно, попробуйте отключить / удалить их.

Проблема может быть подключена к одной из вас, вы можете попробовать воспроизведение проблемы с помощью «воспроизведения» ваших журналов доступа на вашем сервере постановки / разработки.

Если ничего не работает, если бы это был я, я бы сделал следующее: - пытаясь реплицировать проблему на «пустой» экземпляре магнолии (без какого-либо моего кода) - пытаясь реплицировать проблему на «пустой» экземпляр магнолии (без 3-й партийных модулей) - пытаться обновить все программы (Magnolia, 3-й партийные модули, JVM) - наконец попытайтесь запустить производственную площадку с помощью Yourkit и попробуйте найти утечку

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

HTH, Ян

Похоже, что утечки памяти происходят из ваших массивов.У сборщика мусора возникают проблемы с идентификацией экземпляров объектов, которые были удалены из массивов, поэтому они не будут собраны для освобождения памяти.Мой совет: когда вы удаляете объект из массива, назначьте позицию бывшего объекта null, поэтому сборщик мусора может понять, что это null объект и удалите его.Сомневаюсь, что это именно ваша проблема, но всегда полезно знать эти вещи и проверить, не ваша ли это проблема.

Также полезно назначить экземпляр объекта null когда вам нужно его удалить/почистить.Это потому, что finalize() Метод отрывочный и злой, и иногда сборщик мусора не вызывает его.Лучший обходной путь — вызвать его (или другой подобный метод) самостоятельно.Таким образом, вы можете быть уверены, что очистка мусора прошла успешно.Как сказал Джошуа Блох в своей книге:Эффективная Java, 2-е издание, пункт 7, стр. 27:Избегайте финализаторов.«Финализаторы непредсказуемы, часто опасны и вообще ненужны».Вы можете посмотреть раздел здесь.

Поскольку код не отображается, я не вижу, может ли какой-либо из этих методов быть полезным, но знать эти вещи все равно стоит.Надеюсь, эти советы вам помогут!

Как рекомендуется выше, я свяжусь с Devs of Magnolia, но между тем:

Вы получаете эту ошибку, потому что GC не собирается много на прогон

Одновременный коллектор бросит OutofMeMoryError, если слишком много Время проводится в сборке мусора: если более 98% Общее время проводится в сборке мусора и менее 2% кучи Восстановлено, будет брошена OutofMemoryError.

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

Вот пример конфигурации, просто чтобы начать начать параметры, вам придется выяснить ваше сладкое место. Журналы GC, вероятно, помогут для этого

Мои виртуальные параметры следующие: -Xms= 6g. -Xmx= 6G -Xx: maxpermsize= 1G -Xx: Newsize= 2G -Xx: maxtenurgythold= 8 -Xx: Survivorratio= 7 -Xx: + useconcmarksweepgc -Xx: + CMSClassunloadingNinggence -Xx: + CMSPERMGensweepingeNabled -Xx: CMSINITITITINGOCCCUPANDINGFRACT= 60 -Xx: + HeapdumponoutofmemoryError -Xx: + printgcdetails -Xx: + printgeptimestams -Xx: + printteningdistribution -Xloggc: logs / gc.log

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top