Подходы, основанные на отказоустойчивости, чтобы избежать ошибки java.lang.OutOfMemoryError

Многие тщательно разработанные фрагменты кода Java были потеряны из-за java.lang.OutOfMemoryError. Кажется, от этого нет никакого облегчения, даже код производственного класса нарушается.

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

Итак, инструменты, имеющиеся в распоряжении Java-программистов, выглядят так:

  1. java.lang.Runtime.addShutdownHook (Перехватчик потока) - перехватчики отключения позволяют плавно упасть.
  2. java.lang.Runtime.freeMemory () - позволяет нам проверить доступную для виртуальной машины память

Итак, у меня возникла мысль: можно ли написать фабричные методы, которые перед созданием объектов проверяют, достаточно ли в системе осталось памяти, прежде чем пытаться выделить память? Например, в C malloc завершится неудачно, и вы узнаете, что у вас закончилась память, что не идеальная ситуация, но вы не просто упадете замертво от аневризмы java.lang.OutOfMemoryError.

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

  1. Я использую микро-экземпляр Amazon
  2. Я могу выделить для моей виртуальной машины очень мало памяти, скажем, 400 МБ
  3. Мой процесс Java обрабатывает задания в многопоточном режиме, каждый поток потребляет переменный объем памяти в зависимости от параметров вычислительной задачи.
  4. Предположим, что у моего процесса нет утечек памяти
  5. Теперь, если я продолжу кормить его работой до того, как она будет завершена, он в конечном итоге умрет от голода памяти.
  6. Если я установлю -Xmx слишком высоко - я получу подкачку и, возможно, сбой в ОС
  7. Если я установлю верхний предел одновременных операций - это может быть неоптимально, поскольку я могу ограничить принятие задания, которое может быть выполнено с доступной оперативной памятью, или, что еще хуже, принять задание, требующее МНОГО памяти, и в конечном итоге попадает в java.lang .OutOfMemoryError в любом случае. X. Надеюсь, что это поможет объяснить мотивацию вопроса - я думаю, что стандартные ответы не исключают друг друга в поисках отказоустойчивого подхода к проблеме.

Заранее спасибо.


person user1172468    schedule 27.09.2012    source источник
comment
Переключиться на 64-битную JVM и установить много-много памяти? У вас заканчивается память, потому что вы выделяете слишком много (намеренно или из-за утечки). Сначала выясните, не протекает ли у вас утечка, а если не попытайтесь действовать более эффективно. Или переключитесь на 64-битную JVM с большим объемом памяти.   -  person zmbq    schedule 28.09.2012
comment
Привет @zmbq, спасибо за ответ, я добавлю заметки на основе вашего ответа.   -  person user1172468    schedule 28.09.2012
comment
Даже если есть способ узнать, скоро ли у вас закончится память, как логика вашего приложения справится с этим? Проблема в том, как говорит @zmbq: вашему приложению требуется много ОЗУ, и в этом случае вам нужно либо сделать так, чтобы оно не использовало столько ОЗУ, либо добавить ОЗУ, либо перейти на 64-разрядную версию; или у вас утечка памяти, и в этом случае вам следует исправить свой код - даже с большим объемом оперативной памяти у вас в конечном итоге закончится. Я не думаю, что это очень хороший вопрос, но его можно улучшить, если, например, вы действительно используете много ОЗУ, вы можете объяснить, для чего он нужен, и, возможно, мы сможем предложить предложения, как уменьшить использование.   -  person gregmac    schedule 28.09.2012
comment
Каковы компромиссы производительности при постоянном выполнении проверки оставшейся памяти при создании каждого объекта? Я, кажется, более склонен уменьшить подверженность ошибкам на этапе проектирования, нагрузочного тестирования, а также вводить ограничения на полезную нагрузку данных, которые могут привести к ошибке. но это вечно великая тема. с нетерпением жду дополнительных ответов.   -  person amphibient    schedule 28.09.2012
comment
Привет @gregmac, если вы посмотрите на мой обновленный вопрос - я надеюсь, что это объяснит сценарий. Так, например, если я привязан к оперативной памяти, я мог бы опросить доступную оперативную память перед выделением нового объекта, если доступная оперативная память меньше некоторого установленного параметра, либо выполните блокировку ленивого вращения, либо отклоните запрос. Идея состоит в том, что у вас есть объекты, потребляющие оперативную память, которые являются временными через систему, они в конечном итоге будут освобождены, но как раз в эту секунду я не хочу выделять больше объектов.   -  person user1172468    schedule 28.09.2012
comment
@foampile, я, честно говоря, не знаю ответа на этот вопрос, это хороший момент - я не знаю, насколько дорог метод freeMemory (). Теперь, если мы сравним его с malloc в C - malloc вернет 0, если он не может выделить память, и это делается при каждом распределении динамической памяти, что происходит много в любом нетривиальном фрагменте кода.   -  person user1172468    schedule 28.09.2012
comment
Насколько мне известно, нет способа проверить, сколько полезной памяти осталось из-за сборки мусора. GC обычно запускается, когда он близок к пределу или в зависимости от схемы.   -  person gigadot    schedule 28.09.2012
comment
извините - я перечитал вопрос и вижу, что у вас абсолютный предел памяти. Так что то, что я написал, на самом деле не применимо.   -  person Sam Goldberg    schedule 28.09.2012
comment
На самом деле - мне пришло в голову, что вы думали, что с этим можно справиться на C. Итак, как бы вы спроектировали свое многопоточное приложение на C, чтобы предотвратить нехватку памяти? Простая проверка возврата malloc не поможет приложению продолжать работу, поскольку любой из потоков может достичь предела памяти в любой случайный момент времени, и тогда вам нужно будет написать какой-то механизм восстановления. Я думаю, что ответ может заключаться в том, чтобы спроектировать это на C и посмотреть, может ли тот же подход работать на Java. Я не думаю, что это тривиальное решение даже на C.   -  person Sam Goldberg    schedule 28.09.2012


Ответы (2)


Мы обращались с памятью JVM больше как с параметром настройки, чем как с чем-то, что необходимо активно управлять приложением. У нас есть класс MemoryInfo (который включает в себя несколько методов информации о памяти во время выполнения).

Во время работы приложения мы отслеживаем свободную память в приложении как:

 Runtime.getMaxMemory() - Runtime.getTotalMemory() + Runtime.getFreeMemory();

Максимальный объем памяти равен -Xmx jvm arg, общий объем памяти - это то, что JVM уже выделила для кучи приложения, а свободная память - это то, сколько выделенной памяти кучи все еще доступно. (Если ваш параметр -Xms совпадает с вашим параметром -Xmx, тогда getFreeMemory() - это все, что вам нужно проверить).

Если мы используем более 70% памяти, мы отправляем оповещения в нашу систему мониторинга. На этом этапе мы принимаем решение, можем ли мы лимпить до конца дня, или нужно изменить параметр -Xmx и перезапустить игру. Хотя это кажется немного запутанным, на практике после настройки системы мы никогда не сталкиваемся с проблемами памяти. (Как только вы получите более 90% максимальной используемой памяти, JVM будет очень часто собирать мусор, чтобы попытаться предотвратить нехватку памяти).

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

Тем не менее, наш подход состоит в том, чтобы сохранить как можно больше в памяти и просто выделить много оперативной памяти. В наших больших системах выделено 28 ГБ ОЗУ (в среднем мы используем от 40 до 60% от этого объема).

person Sam Goldberg    schedule 27.09.2012
comment
спасибо за ответ - вот мои комментарии: 1. Я согласен, когда я работаю на своем большом железе - я настраиваю выделять много оперативной памяти - затем молитесь (это часть, с которой мне не очень комфортно) ... 2. Я согласен, что проверка freemem при каждом распределении объектов может быть нереалистичной 3. Однако могут быть реализованы альтернативы, такие как протектор домашнего хозяйства устанавливает статическую переменную в служебном классе на stopAllocatingNewObjects = true при достижении определенного порога ... и когда вы опускаетесь ниже другого порога, вы переворачиваете его обратно, чтобы он сгорел. Фабрики проверяют этот флаг перед созданием объектов. - person user1172468; 28.09.2012
comment
Кроме того, вопрос действительно возникает, когда я работаю с потоковой обработкой заданий на большом количестве микро-экземпляров AWS. Спасибо. - person user1172468; 28.09.2012
comment
@ user1172468: Я написал ответ до того, как увидел ваши ограничения. Основываясь на том, что вы сказали, я бы попробовал простой подход, пытаясь определить минимальный объем свободной памяти, необходимый перед запуском задания, или какой-либо другой вид подсчета объектов, который дает вам представление о том, нужно ли потоку спать, пока не освободится больше . - person Sam Goldberg; 28.09.2012
comment
Я согласен с тем, что вы сказали. Мое намерение состояло в том, чтобы посмотреть, сможем ли мы заставить сообщество перечислить устоявшиеся подходы к этой проблеме. Еще раз спасибо за интересное обсуждение. - person user1172468; 28.09.2012
comment
@SamGoldberg - в какой части вашего приложения вы выполняете эту проверку? просто любопытно, где он у вас находится и как часто вы его выполняете. Благодарность - person amphibient; 28.09.2012
comment
@foampile: У нас есть фоновый поток, который выполняет периодические статистические отчеты по приложению. Статистика памяти сообщается раз в минуту (этого достаточно для нашего приложения). Ключ, конечно же, генерирует предупреждение, если мы пропускаем 70% использования памяти JVM. - person Sam Goldberg; 28.09.2012
comment
спасибо, Сэм - я подумал, что это что-то в этой мелодии. мне нравится твоя установка. - person amphibient; 28.09.2012

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

Поскольку у вас нет неограниченного количества ядер, в любом случае слишком много потоков - плохая идея.

person zmbq    schedule 27.09.2012
comment
привет @zmbq, поэтому процесс не обязательно должен быть привязан к ЦП ... например, скребок, поток будет блокироваться большую часть времени, память может зависеть от размера страницы, которую он скребет, поэтому ограничение количество потоков не гарантирует ни: 1. того, что вы не столкнетесь с нехваткой памяти - ИЛИ - 2. что вы будете оптимально использовать свои вычислительные ресурсы. - person user1172468; 28.09.2012
comment
Что ж, если вы не знаете, сколько памяти потребуется одному потоку, вы можете исчерпать память, обработав всего один запрос. - person zmbq; 28.09.2012
comment
@ user1172468: Выполняли ли вы какое-либо профилирование (например, с помощью Jprobe или MyKit), посмотрите, как увеличивается память в различных точках вашего приложения. Судя по вашему последнему комментарию, размер очищаемой страницы может быть хорошим индикатором того, сколько памяти требуется для работы. Можете ли вы использовать размер страницы как простой показатель для прогнозирования потребности в памяти? Иногда статистическая вероятность бывает достаточно хорошей (и абсолютная уверенность не требуется). - person Sam Goldberg; 28.09.2012
comment
@Sam, вот что я подумал, основываясь на вашем комментарии. 1: Я собираюсь собрать симулятор - доказательство в пудинге. 2: Я думаю, что обычно используемый подход является надежным, когда вы имеете дело с большими объемами памяти по отношению к вашей рабочей нагрузке - я подозреваю, что он не такой надежный (как я узнал из некоторых облачных приложений, над которыми я сейчас работаю) . 3: Используя прогнозирование, вы не можете довести свою систему до уровня, близкого к оптимальному, вам нужно оставить много свободного места для избыточности. - person user1172468; 28.09.2012