Сейчас 6 утра. Я бодрствую, суммируя последовательность событий, ведущих к моему раннему пробуждению. Когда эти истории начались, мой телефонный звонок сработал. Сонный и раздражительный я проверил телефон, чтобы увидеть, действительно ли я достаточно сумасшедший, чтобы установить будильник в 5 утра. Нет, это была наша система мониторинга, показывающая, что одна из служб Plumbr вышла из строя .
Как опытный ветеран в области, я сделал первый правильный шаг к решению проблемы, включив кофемашину. С чашкой кофе я был готов решать проблемы. Первый подозрение, само приложение, казалось, работало совершенно нормально до сбоя. Никаких ошибок, никаких предупреждающих знаков, никаких следов подозреваемых в журналах приложений.
Мониторинг, который мы провели, обнаружил смерть процесса и уже перезапустил сбойную службу. Но поскольку у меня уже был кофеин в моей крови, я начал собирать больше доказательств. 30 минут спустя я обнаружил, что смотрю на следующее в /var/log/kern.log :
1
2
|
Jun 4 07 : 41 : 59 plumbr kernel: [ 70667120.897649 ] Out of memory: Kill process 29957 (java) score 366 or sacrifice child Jun 4 07 : 41 : 59 plumbr kernel: [ 70667120.897701 ] Killed process 29957 (java) total-vm:2532680kB, anon-rss:1416508kB, file-rss:0kB |
По-видимому, мы стали жертвами ядра Linux. Как вы все знаете, Linux построен с кучей нечестивых существ (называемых « демонами »). Эти демоны управляются несколькими заданиями ядра, одна из которых кажется особенно зловещей. Очевидно, что все современные ядра Linux имеют встроенный механизм « Out Of Memory killer », который может уничтожить ваши процессы в условиях крайне низкого уровня памяти. Когда такое условие обнаружено, убийца активируется и выбирает процесс для уничтожения. Цель выбирается с использованием набора эвристических оценок, оценивающих все процессы и выбирающих тот, с наименьшим количеством очков для уничтожения.
Понимание «убийцы нехватки памяти»
По умолчанию ядра Linux позволяют процессам запрашивать больше памяти, чем доступно в настоящее время в системе. Это имеет весь смысл в мире, учитывая, что большинство процессов фактически никогда не используют всю память, которую они выделяют. Самое простое сравнение с этим подходом было бы с кабельными операторами. Они продают всем потребителям обещание загрузки 100 Мбит, намного превышающее фактическую пропускную способность, присутствующую в их сети. Ставка опять делается на то, что пользователи не будут одновременно использовать свой выделенный лимит загрузки. Таким образом, одна 10-гигабитная ссылка может успешно обслуживать более 100 пользователей, которые допускает наша простая математика.
Побочный эффект такого подхода виден в том случае, если некоторые из ваших программ находятся на пути истощения системной памяти. Это может привести к крайне низким условиям памяти, когда никакие страницы не могут быть выделены для обработки. Возможно, вы столкнулись с такой ситуацией, когда даже учетная запись root не может убить оскорбительную задачу. Чтобы предотвратить такие ситуации, убийца активирует и определяет процесс, который будет убит.
Вы можете узнать больше о тонкой настройке поведения « Out of memory killer » из этой статьи в документации RedHat .
Что вызвало убийство нехватки памяти?
Теперь, когда у нас есть контекст, все еще неясно, что вызвало «убийцу» и разбудило меня в 5 утра? Еще одно расследование показало, что:
- Конфигурация в / proc / sys / vm / overcommit_memory позволяла перезаписывать память — она была установлена в 1, указывая, что каждый malloc () должен быть успешным.
- Приложение работало на экземпляре EC2 m1.small. Экземпляры EC2 отключили подкачку по умолчанию.
Эти два факта в сочетании с внезапным всплеском трафика в наших сервисах привели к тому, что приложение запросило все больше памяти для поддержки этих дополнительных пользователей. Конфигурация с чрезмерной загрузкой позволила выделить все больше и больше памяти для этого жадного процесса, в конечном итоге вызвав « убийцу нехватки памяти », который делал именно то, для чего предназначен. Убить наше приложение и разбудить меня среди ночи.
пример
Когда я описал поведение инженеров, один из них был достаточно заинтересован, чтобы создать небольшой контрольный пример, воспроизводящий ошибку. Когда вы компилируете и запускаете следующий фрагмент кода Java в Linux (я использовал последнюю стабильную версию Ubuntu):
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
package eu.plumbr.demo; public class OOM { public static void main(String[] args){ java.util.List l = new java.util.ArrayList(); for ( int i = 10000 ; i < 100000 ; i++) { try { l.add( new int [100_000_000]); } catch (Throwable t) { t.printStackTrace(); } } } } |
тогда вы столкнетесь с тем же самым Недостаточно памяти: убейте процесс <PID> (java) счет <SCORE> или пожертвуйте дочернее сообщение.
Обратите внимание, что вам может понадобиться настроить размер файла подкачки и кучи, в моем тестовом примере я использовал кучу 2g, указанную через -Xmx2g, и следующую конфигурацию для подкачки:
1
2
3
4
|
swapoff -a dd if =/dev/zero of=swapfile bs= 1024 count= 655360 mkswap swapfile swapon swapfile |
Решение?
Есть несколько способов справиться с такой ситуацией. В нашем примере мы только что перенесли систему в экземпляр с большей памятью. Я также рассмотрел возможность замены, но после консультации с инженерами мне напомнили о том, что процессы сборки мусора в JVM не очень хорошо работают при замене, поэтому этот вариант был снят с рассмотрения.
Другие возможности включают тонкую настройку OOM killer , масштабирование нагрузки по горизонтали между несколькими небольшими экземплярами или снижение требований к памяти приложения.
Если исследование показалось вам интересным — следите за Plumbr в Твиттере или RSS , мы будем публиковать наши обзоры о Java.
Ссылка: | Недостаточно памяти: убейте или пожертвуйте ребенка от нашего партнера по JCG Яана Ангерпикка в блоге Plumbr Blog . |