Статьи

Недостаточно памяти: убей процесс или пожертвуй ребенком

убить-процесс или жертвенность-ребенок Сейчас 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 .