У нас есть утечка памяти, вызванная сценариями GroovyShell/ Groovy (см. код GroovyEvaluator в конце). Основные проблемы (копипаст из MAT анализатора):
Класс "java.beans.ThreadGroupContext", загружаемый "‹системным загрузчиком классов›", занимает 807 406 960 (33,38%) байт.
а также:
16 экземпляров «org.codehaus.groovy.reflection.ClassInfo$ClassInfoSet$Segment», загруженных «sun.misc.Launcher$AppClassLoader @ 0x7004e9c80», занимают 1 510 256 544 (62,44%) байта.
Мы используем Groovy 2.3.11 и Java8 (точнее, 1.8.0_25).
Обновление до Groovy 2.4.6 не решает проблему. Просто улучшает использование памяти a немного немного, особенно. не кучи.
Используемые аргументы Java: -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC
Кстати, я прочитал https://dzone.com/articles/groovyshell-and-memory-leaks. Мы устанавливаем для оболочки GroovyShell значение null, когда она больше не нужна. Использование GroovyShell().parse(), вероятно, помогло бы, но на самом деле это не вариант для нас - у нас есть> 10 наборов, каждый из которых состоит из 20-100 скриптов, и их можно изменить в любой момент. время (во время выполнения).
Установка MaxMetaspaceSize также должна помочь, но на самом деле она не решает основную проблему, не устраняет основную причину. Так что я все еще пытаюсь прибить его вниз.
Я создал нагрузочный тест, чтобы воссоздать проблему (см. код в конце). Когда я запускаю его:
- размер кучи, размер метапространства и количество классов продолжают увеличиваться
- дамп кучи, сделанный через несколько минут, больше 4 ГБ
Графики производительности за первые 3 минуты:
Как я уже упоминал, я использую MAT для анализа дампов кучи. Итак, давайте проверим отчет дерева Dominator:
Hashmap занимает> 30% кучи. Итак, давайте проанализируем это дальше. Посмотрим, что в нем сидит. Давайте проверим записи хэша:
Он сообщает о 38 830 сообщениях. Включая 38 780 записей с ключами, соответствующими ".class Script."
Другое дело, отчет "дублирующиеся классы":
У нас есть 400 записей (поскольку нагрузочные тесты определяют 400 G.scripts), все для классов "ScriptN". Все они содержат ссылки на groovyclassloader$innerloader.
Я обнаружил похожую ошибку: https://issues.apache.org/jira/browse/GROOVY-7498 (см. комментарии в конце и прикрепленный скриншот) - их проблемы были решены путем обновления Java до 1.8u51. Хотя нам это не помогло.
Наш код:
public class GroovyEvaluator
{
private GroovyShell shell;
public GroovyEvaluator()
{
this(Collections.<String, Object>emptyMap());
}
public GroovyEvaluator(final Map<String, Object> contextVariables)
{
shell = new GroovyShell();
for (Map.Entry<String, Object> contextVariable : contextVariables.entrySet())
{
shell.setVariable(contextVariable.getKey(), contextVariable.getValue());
}
}
public void setVariables(final Map<String, Object> answers)
{
for (Map.Entry<String, Object> questionAndAnswer : answers.entrySet())
{
String questionId = questionAndAnswer.getKey();
Object answer = questionAndAnswer.getValue();
shell.setVariable(questionId, answer);
}
}
public Object evaluateExpression(String expression)
{
return shell.evaluate(expression);
}
public void setVariable(final String name, final Object value)
{
shell.setVariable(name, value);
}
public void close()
{
shell = null;
}
}
Нагрузочный тест:
/** Run using -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC */
public class GroovyEvaluatorLoadTest
{
private static int NUMBER_OF_QUESTIONS = 400;
private final Map<String, Object> contextVariables = Collections.emptyMap();
private List<Fact> factMappings = new ArrayList<>();
public GroovyEvaluatorLoadTest()
{
for (int i=0; i<NUMBER_OF_QUESTIONS; i++)
{
factMappings.add(new Fact("fact" + i, "question" + i));
}
}
private void callEvaluateExpression(int iter)
{
GroovyEvaluator groovyEvaluator = new GroovyEvaluator(contextVariables);
Map<String, Object> factValues = new HashMap<>();
Map<String, Object> answers = new HashMap<>();
for (int i=0; i<NUMBER_OF_QUESTIONS; i++)
{
factValues.put("fact" + i, iter + "-fact-value-" + i);
answers.put("question" + i, iter + "-answer-" + i);
}
groovyEvaluator.setVariables(answers);
groovyEvaluator.setVariable("answers", answers);
groovyEvaluator.setVariable("facts", factValues);
for (Fact fact : factMappings)
{
groovyEvaluator.evaluateExpression(fact.mapping);
}
groovyEvaluator.close();
}
public static void main(String [] args)
{
GroovyEvaluatorLoadTest test = new GroovyEvaluatorLoadTest();
for (int i=0; i<995000; i++)
{
test.callEvaluateExpression(i);
}
test.callEvaluateExpression(0);
}
}
public class Fact
{
public final String factId;
public final String mapping;
public Fact(final String factId, final String mapping)
{
this.factId = factId;
this.mapping = mapping;
}
}
Есть предположения? Спасибо заранее