Программа Java становится медленнее после запуска на некоторое время

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

for (int iter=0; iter<1000; iter++) {
    // 1. Create many temporary variables and do some computations                         
    // 2. Update the value for the parameters                    
}

Вычисления параметров обновления довольно сложны, и мне приходится создавать много временных объектов, но они не упоминаются вне цикла. Код в цикле имеет интенсивность процессора и не имеет доступа к диску. Эта программа загружает относительно большой учебный набор данных, поэтому я предоставил 10G-памяти (-Xmx10G) для JVM, которая намного больше, чем требуется (пик при ~ 6G с помощью команды "сверху" или диспетчера задач окна).

Я тестировал его на нескольких Linux-машинах (память centos 6, 24G) и оконную машину (win7, 12G), как с установленным SUN Hotspot JDK/JRE 1.8. Я не указывал другие параметры JVM, кроме -Xmx. Обе машины посвящены моей программе.

В окнах моя программа работает хорошо: каждая итерация использует очень похожее время работы. Тем не менее, время работы на всех станциях centos является странным. Он изначально работает правильно, но резко замедляется (~ 10 раз медленнее) на 7-й/8-й итерации, а затем замедляет ~ 10% на каждой итерации.

Я подозреваю, что это может быть вызвано сборщиком мусора Java. Поэтому я использую jconsole для мониторинга моей программы. Незначительный GC происходит очень часто на обеих машинах, потому что программа создает много временных переменных в цикле. Кроме того, я использовал команду jstat -gcutil $pid $1s и зафиксировал статистику:

Centos: https://www.dropbox.com/s/ioz7ai6i1h57eoo/jstat.png?dl=0

Окно: https://www.dropbox.com/s/3uxb7ltbx9kpm9l/jstat-winpng.png?dl=0

[Edited] Однако статистика по двум типам машин сильно различается:

  • "S1" на окнах быстро перескакивает от 0 до 50, а при "0,00" - в процентах.
  • "E" в окнах очень быстро изменяется от 0 до 100. Когда я печатаю стат каждую секунду, снимок экрана не показывает его прирост до 100. На centos, однако, "E" увеличивается довольно медленно к 100, и затем сводится к 0 и снова увеличивается.

Кажется, странное поведение моей программы связано с Java GC? Я новичок в мониторинге производительности Java и не имею хорошей идеи для оптимизации настройки параметров GC. Есть ли у вас какие-либо предложения? Большое вам спасибо!

Ответы

Ответ 1

Мне жаль, что опубликовано это как ответ, но у меня недостаточно баллов для комментариев.

Если вы считаете, что это проблема, связанная с GC, я бы ее заменил для Мусора 1 Collector -XX: + UseG1GC

Я нашел это краткое объяснение: http://blog.takipi.com/garbage-collectors-serial-vs-parallel-vs-cms-vs-the-g1-and-whats-new-in-java-8/

Можете ли вы запустить свое программное обеспечение под профилированием? Попробуйте использовать jprofiler, VisualVM или даже профилировщик netbeans. Это может помочь вам.

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

Извините еще раз за то, что вы не комментируете. (Это было бы более уместно)

Ответ 2

Предоставление Java (или любого языка сбора мусора) слишком много памяти отрицательно сказывается на производительности. Живые объекты (ссылки) становятся все более разреженными в памяти, что приводит к более частому извлечению из основной памяти. Обратите внимание, что в примерах, которые вы показали нам, более быстрые окна делают более быстрый и полный GC, чем Linux, но GC-циклы (особенно полные gcs) обычно плохо для производительности.

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

Более радикальное решение, но такое, которое должно иметь большое влияние, - это исключить (или уменьшить как можно больше) создание объекта в цикле путем утилизации объектов в пулах.

Ответ 3

Я бы рассмотрел объявление варов вне цикла, так что распределение памяти выполняется один раз и полностью исключает GC.

Ответ 4

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

Ответ 5

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

Ответ 6

Если время GC составляет сотни миллисекунд, как показано на скриншоте, тогда GC, скорее всего, не проблема. Я предлагаю вам изучить конфликт блокировок и, возможно, IO, используя профилировщик (Netbeans отлично). Я знаю, что вы заявили, что ваша программа сделала очень мало ввода-вывода, но с профилированием (как и отладка) вам нужно удалить все ваши предположения и шаг за шагом.

Ответ 7

По моему опыту JAVA требуется достаточно памяти и 2+ процессора. В противном случае использование ЦП будет очень обширным при запуске GC.