Java Garbage Collector - не работает нормально через регулярные промежутки времени

У меня есть программа, которая постоянно работает. Обычно, кажется, мусор собирает и остается под примерно 8 МБ использования памяти. Однако каждый уик-энд отказывается собирать мусор, пока я не сделаю явный призыв к нему. Однако, если он приближается к максимальному размеру кучи, он все равно будет собирать мусор. Однако единственная причина, по которой эта проблема была замечена, заключается в том, что она на самом деле потерпела крах из-за нехватки памяти в один уик-энд, то есть она должна была достичь максимального размера кучи и не запускать сборщик мусора.

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

Что здесь происходит?

EDIT:

Хорошо, из комментариев, кажется, я не предоставил достаточно информации. Программа просто получает поток UDP-пакетов, которые помещаются в очередь (устанавливается на максимальный размер 1000 объектов), которые затем обрабатываются для хранения своих данных в базе данных. В среднем он получает около 80 пакетов в секунду, но может достигать максимума до 150. Он работает под управлением Windows Server 2008.

Дело в том, что эта деятельность довольно последовательна, и, если угодно, то в то время, когда начинается использование памяти, она неуклонно поднимается, активность должна быть ниже, а не выше. Имейте в виду, что график, который я опубликовал выше, является единственным, который у меня есть, который простирается настолько далеко, поскольку я только изменил оболочку Java Visual VM, чтобы сохранить данные графика достаточно далеко, чтобы увидеть его на этой неделе, поэтому я понятия не имею, в то же время каждую неделю, потому что я не могу наблюдать за выходными, поскольку это в частной сети, и я не работаю в выходные.

Вот график следующего дня: alt text

Это в значительной степени то, что использование памяти выглядит как каждый день недели. Из-за этой проблемы программа никогда не перезапускается, и мы сообщаем об этом сборщику мусора в понедельник утром. Одна неделя мы попытались перезапустить его в пятницу днем, и он все равно начал подниматься на какое-то время в выходные, поэтому время, когда мы перезапускаем его, похоже, не имеет никакого отношения к использованию памяти на следующей неделе.

Тот факт, что он успешно мусор собирает все эти объекты, когда мы говорим ему, подразумевает, что объекты собираются, он просто не делает этого, пока не достигнет максимального размера кучи, или мы явно вызываем сборщик мусора. Дамп кучи ничего нам не говорит, потому что, когда мы пытаемся выполнить его, он внезапно запускает сборщик мусора, а затем выводит кучу кучи, что, конечно же, выглядит совершенно нормально на данный момент.

Итак, я полагаю, у меня есть два вопроса: почему это внезапно не мусор, собирающий то, как он делает всю оставшуюся часть недели, и почему это происходит в один случай, сбор мусора, который возникает, когда он достигает максимального размера кучи, неспособный собрать все эти объекты (т.е. почему есть ссылки на так много объектов, что один раз, когда в другой раз не должно быть)?

UPDATE:

Это утро было интересным. Как я уже упоминал в комментариях, программа работает в клиентской системе. Наш контакт в организации-клиенте сообщает, что в 1 утра эта программа потерпела неудачу, и он должен был перезапустить ее вручную, когда он вошел в работу этим утром, и что еще раз время сервера было неправильным. Это вопрос, который у нас был с ними в прошлом, но до сих пор этот вопрос никогда не был связан.

Просматривая журналы, которые производит наша программа, мы можем вывести следующую информацию:

  • В 01:00 сервер каким-то образом повторил его время, установив его на 00:28.
  • В 00:45 (согласно новому, некорректному серверному времени) один из потоков обработки сообщений в программе выкинул ошибку из памяти.
  • Однако другой поток обработки сообщений (есть два типа сообщений, которые мы получаем, они обрабатываются несколько по-другому, но они оба постоянно входят), продолжает работать, и, как обычно, использование памяти продолжает расти никакой сборки мусора (как видно из графиков, которые мы записывали, еще раз).
  • В 00:56 журналы останавливаются примерно до 7 утра, когда программа была перезапущена нашим клиентом. Тем не менее, график использования памяти за это время все еще неуклонно возрастал.

К сожалению, из-за изменения времени сервера это делает время на нашем графике использования памяти ненадежным. Однако, похоже, он пытался собрать мусор, не смог, увеличил кучу пространства до максимального доступного размера и сразу же уничтожил этот поток. Теперь, когда максимальное пространство кучи увеличилось, он счастлив использовать все это, не выполняя крупную сборку мусора.

Итак, теперь я спрашиваю об этом: если время сервера внезапно меняется, как это было, может ли это вызвать проблему с процессом сбора мусора?

Ответы

Ответ 1

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

Я думаю, что ваш диагноз неверен. Если в вашей JVM не будет что-то серьезно нарушенное, приложение просто выбросит OOME после того, как он просто запустит полный сбор мусора, и обнаружил, что у него все еще недостаточно свободной кучи, чтобы продолжить *.

Я подозреваю, что здесь происходит одно или несколько из следующих действий:

  • У вашего приложения медленная утечка памяти. Каждый раз, когда вы перезапускаете приложение, утечка памяти возвращается. Таким образом, если вы регулярно перезапускаете приложение в течение недели, это может объяснить, почему он только вылетает в выходные.

  • Ваше приложение выполняет вычисления, требующие различного объема памяти для завершения. В тот уик-энд кто-то отправил ему запрос, который потребовал больше памяти, которая была доступна.

Запуск GC вручную не решит проблему в любом случае. Что вам нужно сделать, так это исследовать возможность утечки памяти, а также посмотреть размер памяти приложения, чтобы убедиться, что он достаточно велик для выполняемых задач.

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

* На самом деле критерии принятия решения о сдаче OOME немного сложнее, чем это. Они зависят от определенных параметров настройки JVM и могут включать процент времени, затрачиваемого на выполнение GC.

Followup

@Ogre - Мне нужно получить гораздо больше информации о вашем приложении, чтобы иметь возможность ответить на этот вопрос (об утечках памяти) с любой спецификой.

С новыми доказательствами есть еще две возможности:

  • Ваше приложение может застревать в цикле, который теряет память из-за деформирования времени.

  • Временная деформация часов может привести к тому, что GC подумает, что он занимает слишком большой процент времени выполнения и вызывает в результате OOME. Это зависит от настроек JVM.

В любом случае, вы должны опираться на своего клиента, чтобы заставить их перестать настраивать системные часы. (32-минутный timewarp - это слишком много!). Попросите их установить системную службу, чтобы синхронизировать часы с сетью по часам в час (или чаще). Критически, заставить их использовать сервис с возможностью регулировки часов с небольшими приращениями.

(Повторная 2-я пуля: в JVM есть механизм мониторинга GC, который измеряет процентное соотношение общего времени, которое JVM тратит на выполнение GC, относительно полезной работы. Это предназначено для предотвращения JVM от шлифования до остановка, когда ваше приложение действительно исчерпало память.

Этот механизм будет реализован путем выборки времени настенных часов в разных точках. Но если время настенных часов набирается в критической точке, легко увидеть, как JVM может подумать, что конкретный запуск GC занял гораздо больше времени, чем это было на самом деле... и вызвать OOME.)

Ответ 2

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

Вот параметры JVM, взятые из Oracle Параметры Java HotSpot VM. (Предполагается, что у вас есть JVM Oracle):

-XX:. HeapDumpPath =/java_pid.hprof

Путь к каталогу или имени файла для кучи свалка. Управляемость. (Представлено в 1.4.2 update 12, 5.0 update 7.)

-XX: -HeapDumpOnOutOfMemoryError

Куча дампа для файла, когда java.lang.OutOfMemoryError выбрасывается. Управляемость. (Представлено в 1.4.2 update 12, 5.0 update 7.)

Ответ 3

Хорошо, ребята, спасибо за вашу помощь. Однако правильный ответ не имел никакого отношения к самой программе.

Похоже, что в то время, когда началось использование памяти, он неуклонно поднимался, сервер синхронизировал это время где-то внутри, хотя наш клиентский ИТ-консультант понятия не имеет, где. Очевидно, откуда бы он ни был, не было хороших часов, так как время было на полчаса позади. Мы отключили эту синхронизацию, и теперь, когда я проверил ее сегодня утром, проблема не возникла. Поэтому, если время в вашей системе внезапно изменится, очевидно, это создает проблемы для сборщика мусора. По крайней мере, это то, что это значит для меня.

Что касается того, почему это не происходило ни в каких других частях нашей системы на этом сервере (которые также написаны на Java), мы, вероятно, просто не заметили, поскольку они не имеют дело с большим количеством объектов, и поэтому они никогда бы не вышли из состояния памяти.

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