2016-05-18 4 views
7

Я загружаю большое количество скриптов Groovy (2.4.6) и запускаю их с помощью GroovyScriptEngineImpl в моем приложении Java 8, и через некоторое время у меня возникает проблема.Java/Groovy - утечка памяти в GroovyClassLoader

Существует несколько вещей, которые вы должны знать:

  • Я должен воссоздать новый GroovyScriptEngineImpl каждый раз, когда я запустить скрипт
  • Я должен воссоздать новый GroovyClassLoader каждый раз, когда я бегу сценарий

Мне нужно сделать это так, чтобы изолировать каждый скрипт в отдельной «среде»: я загружаю некоторые внешние JAR-файлы в загрузчик классов для некоторых скриптов, и я не хочу, чтобы другие скрипты могли использовать классы в ose JAR, когда они исполняются.

Проблема связана с тем, что для каждого скрипта, который я запускаю, GroovyClassLoader создаст новый класс ScriptXXXX и загрузит его, но никогда не выгрузит его.

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

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

  • Добавление -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC в аргументы виртуальной машины Java
  • Добавление -Dgroovy.use.classvalue=true в аргументы виртуальной машины Java
  • Удаление «мета-классы» созданный для каждого класса ScriptXXXX, как показано здесь: Groovy Classes not being collected but not signs of memory leak
  • Очистка кеша и закрытие GroovyClassLoader
  • Использование самоанализа вручную очистить некоторые поля кэширования классов в GroovyScriptEngineImpl
  • и т.д ...

Вот «Кратчайший путь к ГК» для одного из ScriptXXXX класса в анализаторе памяти Eclipse:

Eclipse Memory Analyzer dump

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

Если вы хотите воспроизвести этот вопрос, вот пример кода:

GroovyScriptEngineImpl se; 

while (true) 
{ 
    se = new GroovyScriptEngineImpl(new GroovyClassLoader()); 
    CompiledScript script = se.compile("println(\"hello\")"); 
    script.eval(se.createBindings()); 
} 

Благодаря

UPDATE: После прочтения ответа pczeus, я попытался ограничить Метапространство, и некоторые классы, кажется, разгрузка действительно, и я думаю, что это классы ScriptXXX.

При этом через несколько минут я получаю ошибки Out of Metaspace во время выполнения скрипта.

Вот профиль я получаю с VisualVM:

Profile in the Java VisualVM

И «Путь к ОМУ» в анализаторе памяти Eclipse, ибо ScriptXXX классов действительно пусты (их больше нет экземпляра классов), даже класс по-прежнему указан в гистограмме.

+0

Сколько стоит «большое количество» скриптов? – cjstehno

+0

Потенциально неограниченный. На самом деле их может быть только несколько разных сценариев, но, как я сказал, они должны иметь изолированную среду, и поэтому их перекомпилируют каждый раз. В качестве примера я провел тест в течение 1 часа, который создал около 10000 скриптов и использовал 1 ГБ ОЗУ. И даже при меньшем количестве скриптов приложение будет сервером, который не следует перезапускать слишком часто, поэтому количество классов ScriptXXX будет расти в любом случае. – AntoineB

+0

Что вы хотите сказать, хотите изолировать каждый скрипт в отдельной среде? Если вы используете один и тот же кеш скрипта, то как две разные оценки будут влиять друг на друга? –

ответ

1

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

Поскольку вы используете Java8, и знаете, что вы загружаете тысячи временных классов, вы должны попытаться настроить и ограничить количество MetaSpace и настроить частоту очистки. Прямо из https://blogs.oracle.com/poonam/entry/about_g1_garbage_collector_permanent:

JDK8: Метапространство

В JDK 8, метаданные классов теперь хранятся в родной куче, и это пространство называется Метапространством. Есть несколько новых флагов, добавленных для Metaspace в JDK 8:

-XX: MetaspaceSize = где начальное количество пространства (начальная высота-вода), выделенное для метаданных класса (в байтах), которые могут вызывать мусор для разгрузки классов. Сумма приблизительная. После того, как метка с высокой влажностью будет достигнута, следующая дорожка с высоким уровнем воды будет управляться сборщиком мусора

-XX: MaxMetaspaceSize = где максимальное количество места, которое должно быть выделено для метаданных класса (в байтах) , Этот флаг можно использовать для ограничения объема пространства, выделенного для метаданных класса. Это значение является приблизительным. По умолчанию нет предела. -XX: MinMetaspaceFreeRatio = где минимальный процент от количества метаданных класса, свободный после GC, чтобы избежать увеличения объема пространства (high-water-mark), выделенного для метаданных класса, которые будут вызывать сборку мусора.

-XX: MaxMetaspaceFreeRatio = где максимальный процент метаданных класса метаданных бесплатно после GC, чтобы избежать уменьшения количества пространства (метки высокой воды), выделенного для метаданных класса, которые будут вызывать сборку мусора.

По умолчанию распределение метаданных класса ограничено количеством доступной встроенной памяти. Мы можем использовать новую опцию MaxMetaspaceSize, чтобы ограничить объем встроенной памяти, используемой для метаданных класса. Это аналогично MaxPermSize. Сбор мусора вызывает сбор мертвых загрузчиков классов и классов, когда использование метаданных класса достигает MetaspaceSize (12 Мбайт на 32-битной клиентской VM и 16 Мбайт на 32-битной серверной VM с большими размерами на 64-битных виртуальных машинах). Установите MetaspaceSize на более высокое значение, чтобы отложить сборку индуцированных мусора. После индуцированной сборки мусора может быть увеличено использование метаданных класса, необходимых для создания следующей мусорной коллекции.

+0

Спасибо за ваш ответ, это определенно меняет вещи, но все еще проблемы. Я обновил свой пост с некоторой информацией после попытки вашего решения :) – AntoineB