Проблемы с сборщиком мусора Java и памятью

у меня действительно странная проблема с приложением 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 часов после перезагрузки, а на других серверах с меньшим объемом памяти (4GB!), который работал в течение 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)

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

Ok, так что это проблема с Память, которая проявляется в процессоре, поэтому, если проблема использования памяти решена, то процессор должен быть в порядке, поэтому я взял heapdump, к сожалению, он был слишком большим, чтобы открыть его (файл был 10GB), в любом случае я запускаю сервер locallym загрузил его немного и взял 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, однако я не могу понять, как связать эту подозрительную карту с помощью анализатора памяти eclipse, поскольку он не использует AbstracReferenceMap, это просто обычная хэш-карта.

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

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

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

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

9 ответов


' no-op'finalize() методы определенно должны быть удалены, поскольку они, вероятно, сделают любые проблемы производительности GC хуже. Но я подозреваю, что у вас есть и другие проблемы с утечкой памяти.

Совет:

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

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

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


теперь ваш особые симптомы.

прежде всего, место, где OutOfMemoryErrors были брошены, вероятно, не имеет значения.

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

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

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

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

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


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


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

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

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

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

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

но я считаю, что это сопутствующий симптом, а чем первопричина.


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

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

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

no-op finalize() методы не должны вызывать эту проблему, но они могут усугубить ее. The doc on finalize () показывает, что наличие finalize() метод заставляет GC два раза определите, что экземпляр не используется (до и после вызова finalize()).

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

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


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


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

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

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

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

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

удачи, проблемы с памятью очень сложно отследить.


вы говорите, что уже пробовали jvisualvm, чтобы проверить машину. Может быть, попробуем еще раз, вот так:

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

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

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


  • много раз "странные" ошибки могут быть вызваны агентами java, подключенными к JVM. Если у вас есть какие-либо агенты (например, jrebel/liverebel, newrelic, jprofiler), попробуйте сначала запустить без них.
  • странные вещи также могут произойти при запуске JVM с нестандартными параметрами (- XX); некоторые комбинации, как известно, вызывают проблемы; какие параметры вы используете в настоящее время?
  • утечка памяти также может быть в самой магнолии, вы пробовали погуглить "Магнолия утечка"? Вы используете какие-либо сторонние модули magnolia? Если возможно, попробуйте отключить/удалить их.

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

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


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

HTH, Ян!--1-->


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

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

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


Как рекомендовано выше, я бы связался с разработчиками Magnolia, но тем временем:

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

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

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

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

мои параметры VM следующие: -Отправляй по xms=6Г Значение-Xmx=6Г - XX: MaxPermSize=1G - XX: NewSize=2G - XX: MaxTenuringThreshold=8 - XX: SurvivorRatio=7 - XX: + UseConcMarkSweepGC - XX: + CMSClassUnloadingEnabled - XX: + CMSPermGenSweepingEnabled - XX: CMSInitiatingOccupancyFraction=60 - XX: + HeapDumpOnOutOfMemoryError - XX: + PrintGCDetails - XX: + PrintGCTimeStamps - XX: + PrintTenuringDistribution - Xloggc: журналы / gc.log