Каков наилучший способ справиться с условиями памяти в Java?

У нас есть приложение, которое запускает новые JVM и выполняет код от имени наших пользователей. Иногда у них заканчивается память, и в этом случае ведут себя по-разному. Иногда они бросают OutOfMemoryError, иногда они замирают. Я могу обнаружить последнее очень легким фоновым потоком, который перестает посылать сигналы пульса при низкой загрузке памяти. В этом случае мы убиваем JVM, но мы никогда не можем быть абсолютно уверены в том, какова реальная причина отказа от сердечного приступа. (Это может быть проблема сети или ошибка сегментации.)

Каков наилучший способ надежного обнаружения из памяти условий в JVM?

  • В теории опция -XX: OnOutOfMemoryError выглядит многообещающе, но из-за этой ошибки она эффективно непригодна: https://bugs.openjdk.java.net/browse/JDK-8027434

  • Захват OutOfMemoryError на самом деле не является хорошей альтернативой по известным причинам (например, вы никогда не знаете, где это происходит), хотя он работает во многих случаях.

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

Есть ли альтернативы или обходные пути? Настройки сбора мусора, чтобы заставить JVM прекратить работу, а не замораживать?

EDIT: я полностью контролирую как forking, так и раздвоенную JVM, а также код, выполняемый внутри них, оба работают в Linux, и это нормально использовать специальные утилиты ОС, если это помогает.

Ответ 1

После экспериментов с этим в течение довольно долгого времени это решение, которое сработало для нас:

  • В порожденной JVM поймайте OutOfMemoryError и немедленно выйдите, сигнализируя об отсутствии памяти с кодом выхода на контроллер JVM.
  • В созданной JVM периодически проверяйте количество потребляемой памяти текущего Runtime. Когда объем используемой памяти близок к критическому, создайте файл флага, который сигнализирует об отсутствии памяти для контроллера JVM. Если мы вернемся из этого условия и выйдем нормально, удалите этот файл до выхода.
  • После того, как управляющая JVM присоединяется к разветвленной JVM, он проверяет код выхода, сгенерированный на этапе (1), и файл флага, сгенерированный на этапе (2). В дополнение к этому, он проверяет, существует ли файл hs_err_pidXXX.log и содержит строку "Ошибка вне памяти". (Этот файл генерируется java в случае сбоя.)

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

Явный флаг -XX:OnOutOfMemoryError не использовался из-за проблемы с fork, а -XX:+HeapDumpOnOutOfMemoryError не использовался, потому что куча кучи больше, чем нам нужно.

Решение, безусловно, не самая элегантная часть кода, когда-либо написанная, но выполняла эту работу для нас.

Ответ 2

Единственный реальный вариант - (к сожалению) прекратить работу виртуальной машины как можно скорее.

Поскольку вы, вероятно, не можете изменить весь свой код, чтобы поймать ошибку и ответить. Если вы не доверяете OnOutOfMemoryError (мне интересно, почему он не должен использовать vfork, который используется в Java 8 и работает в Windows), вы можете по крайней мере вызвать heapdump и вести внешний мониторинг этих файлов:

java .... -XX:+HeapDumpOnOutOfMemoryError "-XX:OnOutOfMemoryError=kill %p"

Ответ 3

В случае, если у вас есть контроль как над приложением, так и с конфигурацией, лучшим решением будет найти основную причину выкидывания OutOfMemoryError и исправить это, вместо того чтобы пытаться скрыть симптомы, либо поймав ошибку, либо просто перезапустив JVMs.

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

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