Java8 "java.lang.OutOfMemoryError: Metaspace"

После переключения нашего java-приложения (службы, запущенного на Tomcat) JRE с Java 7 на Java 8, мы начали видеть java.lang.OutOfMemoryError: Metaspace после нескольких дней с большим объемом трафика.

Использование кучи в порядке. Metaspace перескакивает после того, как когда-то исполнялся тот же поток кода во время тестирования производительности.

Что может быть причиной возникновения проблемы с памятью в метаспазме?

Текущие настройки:

-server -Xms8g -Xmx8g -XX:MaxMetaspaceSize=3200m  -XX:+UseParNewGC 
-XX:+UseConcMarkSweepGC -XX:MaxGCPauseMillis=1000 
-XX:+DisableExplicitGC -XX:+PrintGCDetails 
-XX:-UseAdaptiveSizePolicy -XX:SurvivorRatio=7 -XX:NewSize=5004m 
-XX:MaxNewSize=5004m -XX:MaxTenuringThreshold=12 
-XX:CMSInitiatingOccupancyFraction=75
-XX:+UseCMSInitiatingOccupancyOnly -XX:+PrintFlagsFinal  
-XX:+PrintGCDateStamps -XX:+PrintTenuringDistribution 
-XX:+PrintGCCause -XX:+PrintAdaptiveSizePolicy 
-XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=3 -XX:GCLogFileSize=200M 

Также приложение сильно использует отражение. Также мы используем пользовательский загрузчик классов. Все они отлично работали в java 7.

Ответ 1

Я предполагаю, что вы можете создать проблему с тем же запросом (набором запросов) в течение определенного периода времени. Хорошо, что вы определили MaxMetaspaceSize, иначе приложение будет использовать встроенную память, пока не закончится ее рост. Но я начну с следующих шагов:

  • Проверьте, увеличивается ли количество загружаемых в JVM классов для одного и того же запроса, когда вы отправляете его на сервер несколько раз. Если да, возможно, вы создаете динамические классы, которые приведут к росту классов, загруженных в metaspace. Ну, как проверить количество загруженных классов, вы можете использовать visualvm для подключения к серверу с помощью JMX или запуска локально для имитации. Я расскажу о шагах для локальных, но для удаленного подключения JMX вы должны добавить следующее к параметрам JVM для приложения и запустить его и удаленно подключиться к порту 9999 и с помощью -XX: + UnlockDiagnosticVMOptions.
   -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9999 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -XX:+UnlockDiagnosticVMOptions

Когда у вас есть visualvm (jvisualvm), подключенный к JVM, нажмите на монитор, а затем увидите количество загруженных классов. Там вы можете контролировать кучу, а также метапас. Но я добавлю другие инструменты для тщательного контроля метапас.

  1. Также, как только вы подключитесь к jvm, вы можете захотеть сделать снимок кучи и узнать классы, загруженные с помощью OQL. Поэтому перед тем, как вы берете кучу кучи, прекратите запросы на сервер, чтобы вы не попадали на любой запрос/исполняемый код и связанные с ним объекты, но это необязательно. Поэтому после запуска одного и того же набора запросов несколько раз внутри visualvm в "мониторе" щелкните "Дамп кучи" справа вверху. Затем откройте/загрузите моментальный снимок, и вы увидите опцию консоли OQL. вы увидите некоторые предопределенные запросы OQL на правой нижней панели в рамках анализа перменов. Запустите запрос с именем "гистограмма класса загружаемого класса", я думаю, что это даст количество классов, загружаемых каждым загрузчиком классов. Вы можете использовать его для определения того, какой класс загрузчик загружает классы.

выберите карту (sort (map (heap.objects('java.lang.ClassLoader'),  '{loader: it, count: it.classes.elementCount}'), 'lhs.count < rhs.count '),  'toHtml (it) + "
" ')

Но запрос выше, который называется "classloader loaded class", будет медленным, который фактически покажет классы, загружаемые каждым загрузчиком классов.

select { loader: cl,
             classes: filter(map(cl.classes.elementData, 'it'), 'it != null') }
    from instanceof java.lang.ClassLoader cl
  1. Затем попытайтесь проследить рост в области метапасса. Теперь мы будем использовать jconsole и что-то новое, что java имеет: jmc (управление миссией java). Вы можете использовать jconsole для подключения к jvm (локальному или удаленному), и после того, как вы подключены, перейдите на вкладку памяти, и вы сможете отслеживать рост без кучи там, где должен быть кег метаданных и кеш и сжатое пространство класса. И теперь подключитесь

JMC

для подключения к виртуальной машине, а затем после подключения нажмите "Диагностические команды" в JMC, который находится справа. Поскольку мы включили UnlockDiagnosticVMOptions, GC.class_stats может быть выполнена. Вы можете запустить его с отображением всех столбцов и напечатать в csv. Таким образом, команда будет выглядеть так:

GC.class_stats -all=true -csv=true

И затем вы можете сравнить статистику по классам за разные периоды и выяснить, какие классы вызывают проблемы (рост метапроцесса) или какие классы имеют связанную информацию (данные метода/метода) в метапассе. Как анализировать выходы csv, собранные за время: ну, я бы взял этот csv и загрузил его в две аналогичные таблицы (представляющие csv) в базе данных или в другом месте для сравнения выходов csv GC.class_stats, где я могу запускать некоторый SQL или любые другие аналитические инструменты. Это даст лучшее представление о том, что именно растет в метапассе. Статистика GC-класса имеет следующие столбцы:

Index, супер, InstSize, InstCount, InstBytes, зеркало, KlassBytes, K_secondary_supers, VTab, Itab, OopMap, IK_methods, IK_method_ordering, IK_default_methods, IK_default_vtable_indices, IK_local_interfaces, IK_transitive_interfaces, IK_fields, IK_inner_classes, IK_signers, class_annotations, class_type_annotations, fields_annotations, fields_type_annotations, methods_annotations, methods_parameter_annotations, methods_type_annotations, methods_default_annotations, аннотации, Cp, CpTags, CpCache, CpOperands, CpRefMap, CpAll, MethodCount, MethodBytes, ConstMethod, MethodData, StackMap, байт-код, MethodAll, ROAll, RWAll, Всего, ИмяКласса, ClassLoader

Надеюсь, это поможет. Также кажется, что ошибка может быть в Java 8, если она не вызывает утечки в 1.7.

Кроме того, классы не будут выгружены из metaspace, если кто-либо держит любую ссылку на classloader. Если вы знаете, что ваши загрузчики классов должны быть GCed, и никто не должен содержать ссылку на ваш загрузчик классов, вы можете вернуться к дампу кучи в visualvm и щелкнуть экземпляр загрузчика класса и щелкнуть правой кнопкой мыши, чтобы найти "ближайший корень GC", который будет сообщать вы, кто держит ссылку на загрузчиков классов.

Ответ 2

у нас была аналогичная проблема, и основная причина заключалась в том, что файлы классов 60K загружаются в память метаданных, но ничего не выгружается. Добавление ниже JVM arg устраняет проблему.

-Dcom.sun.xml.bind.v2.bytecode.ClassTailor.noOptimize=true

https://issues.apache.org/jira/browse/CXF-2939

Надеюсь, что это поможет.

Ответ 3

Также, если некоторые автоматически развертываются, например, tomcat, НЕ храните резервные копии в tomcat\webapps, в противном случае он может попытаться загрузить резервную копию и столкнуться с этими ресурсами.