#java #garbage-collection #jvm #performance
#java #сбор мусора #jvm #Производительность
Вопрос:
У меня есть веб-приложение Java, работающее на одном экземпляре tomcat. В пиковые периоды веб-приложение обслуживает около 30 страниц в секунду, а обычно около 15.
Моя среда:
O/S: SUSE Linux Enterprise Server 10 (x86_64)
RAM: 16GB
server: Tomcat 6.0.20
JVM: Java HotSpot(TM) 64-Bit Server VM 1.6.0_14
JVM options:
CATALINA_OPTS="-Xms512m -Xmx1024m -XX:PermSize=128m -XX:MaxPermSize=256m
-XX: UseParallelGC
-Djava.awt.headless=true
-verbose:gc -XX: PrintGCDetails -XX: PrintGCTimeStamps"
JAVA_OPTS="-server"
После нескольких дней безотказной работы полный сбор данных начинает происходить чаще, и это становится серьезной проблемой для доступности приложения. После перезапуска tomcat проблема исчезает, но, конечно, возвращается через 5-10 или 30 дней (не согласовано).
Полный журнал GC до и после перезапуска находится на http://pastebin.com/raw.php?i=4NtkNXmi
Он показывает журнал перед перезапуском при времени безотказной работы 6,6 дней, когда приложение страдало, потому что для полной сборки требовалось 2,5 секунды и происходило каждые ~ 6 секунд.
Затем он показывает журнал сразу после перезапуска, где полный GC происходил только каждые 5-10 минут.
У меня есть два дампов, использующих jmap -dump:format=b,file=dump.hprof PID
, когда происходит полный GCS (я не уверен, правильно ли я их понял, когда происходил полный GC или между 2 полными GCS), и открыл их в http://www.eclipse.org/mat / но не получил ничего полезного от подозреваемых в утечке:
- 60 МБ: 1 экземпляр «org.hibernate.impl.SessionFactoryImpl» (я использую hibernate с ehcache)
- 80 МБ: 1024 экземпляра «org.apache.tomcat.util.threads.ThreadWithAttributes» (это, вероятно, 1024 рабочих tomcat)
- 45 МБ: 37 экземпляров «net.sf.ehcache.store.compound.impl.MemoryOnlyStore» (это должны быть мои ~ 37 областей кэша в ehcache)
Обратите внимание, что я никогда не получаю OutOfMemoryError.
Есть идеи о том, где мне искать дальше?
Комментарии:
1. Если у вас на сервере 16 ГБ оперативной памяти, почему бы не использовать больший максимальный размер кучи (-Xmx)?
2. Я никогда не получал OutOfMemoryError, поэтому я подумал, что, поскольку приложение может запускаться, тогда все в порядке. Кроме того, я читал, что предоставление слишком большого объема памяти JVM замедлит полный GC. Это правда?
3. Можете ли вы воспроизвести поведение в среде тестирования? Возможно, с некоторым нагрузочным тестированием. Я отлаживал подобное поведение раньше, но обычно с БОЛЬШОЙ помощью профилировщика (который убьет ваш сервер в рабочей среде).
4. также @cherouvim вы видели oracle.com/technetwork/java/javase/gc-tuning-6-140523.html ? может быть полезно.
5. «Я никогда не получал OutOfMemoryError» — используется не вся память, но происходит полный GC, потому что старое поколение заполнено. Выделение большего объема памяти позволит дольше сохранять объекты в молодом поколении — с большей вероятностью они будут очищены незначительной коллекцией / с меньшей вероятностью будут повышены.
Ответ №1:
Когда у нас возникла эта проблема, мы в конечном итоге отследили ее из-за того, что молодое поколение было слишком маленьким. Хотя мы выделили много оперативной памяти, молодому поколению не была предоставлена ее справедливая доля.
Это означало, что небольшие сборки мусора будут происходить чаще, и это привело к тому, что некоторые молодые объекты были перемещены в постоянное поколение, что также означает более крупные сборки мусора.
Попробуйте использовать -XX:NewRatio
с довольно низким значением (скажем, 2 или 3) и посмотрите, поможет ли это.
Более подробную информацию можно найти здесь.
Ответ №2:
Я переключился с -Xmx1024m
на -Xmx2048m
, и проблема исчезла. Теперь у меня есть 100 дней безотказной работы.
Ответ №3:
Помимо настройки различных опций JVM, я бы также предложил перейти на более новую версию виртуальной машины, потому что в более поздних версиях гораздо лучше настроен сборщик мусора (также без использования нового экспериментального).
Кроме того, если (частично) верно, что назначение большего объема оперативной памяти JVM может увеличить время, необходимое для выполнения GC, существует компромисс между использованием целых 16 ГБ памяти и увеличением объема занимаемой памяти, поэтому вы можете попробовать удвоить все значения, чтобы начать
Xms1024m -Xmx2048m -XX:PermSize= 256m -XX: MaxPermSize = 512m
С уважением
Massimo
Комментарии:
1. Я попробую. Но не слишком ли много maxperm 512? Экземпляр tomcat запускает только 1 приложение с примерно 40 постоянными (спящими) объектами и без spring framework. На этом tomcat не происходит перераспределения, только завершение работы / запуск.
2. Может быть, мои предложенные параметры были всего лишь простым предположением. Я бы указал на обновление JVM как на то, что лучше попробовать, у нас были похожие проблемы (и другие тоже), и они исчезли, когда мы обновили Java до обновления 27. Последнее обновление 29, но у нас были некоторые проблемы с ним.
3. Да, журналы показывают, что PSPermGen составляет около ~ 64 МБ, что, я полагаю, является общим размером загруженного класса: JVM, tomcat, библиотеки и мое приложение. Верно?
Ответ №4:
Что может происходить в вашем случае, так это то, что у вас много объектов, которые живут немного дольше, чем жизненный цикл NewGen. Если оставшееся пространство слишком мало, они переходят прямо к OldGen. -XX: PrintTenuringDistribution
может дать некоторое представление. Ваш NewGen достаточно велик, поэтому попробуйте уменьшить SurvivorRatio
.
кроме того, jconsole, вероятно, предоставит более наглядное представление о том, что происходит с вашей памятью, попробуйте.