О памяти
2020-08-22
Память, она мне нужна. Ноутбук я выбирал, чтобы впихнуть туда как можно больше памяти. Больше 16 гигабайт не влезло. 8 гигов распаяно на материнке. 8 гигов я доставил плашкой DDR4.
Хотя вот сейчас рою спеки. Вообще-то процессор (а контроллер памяти нынче пихают в процессор) умеет до 32 гигов. А в мой UX310UA обычно распаивают 4 гига, а не 8. То бишь, вроде как можно мне заполучить ещё 8 гигов, если воткнуть плашку на 16 гигабайт. Есть у кого попробовать? 😉
Зачем нужна память? Ну, я в работе постоянно использую Intellij IDEA. А она спокойно сжирает пару гигабайт и больше. И в Докере запускаю кучу разных сервисов. И в Хроме постоянно что-то открыто, Gmail и всякие гуглодокументы.
Проблема c Java, Chrome и многими другими приложениями в том, что они видят всю системную память. Все мои 16 гигабайт. И какого-то чорта считают, что все эти 16 гигабайт — их собственные. И пытаются их отожрать.
Ладно, Java сервисы и IDEA я постоянно перезапускаю. Но Chrome через несколько дней аптайма сжирает всё. Сначала кончается память. Потом кончается свап. Потом всё встаёт колом и приходится перезагружаться. Конечно же это происходит в тот момент, когда мне позарез нужно прогнать интеграционные тесты с Elasticsearch в Докере, которым ну вот ещё пару гигабайт памяти нужно прямо сейчас.
На примере Java видно,
насколько прожорливость приложений
действительно зависит от доступной им памяти.
Раньше в JRE 8 был баг.
Если запустить Джаву в Докер контейнере с ограниченным объёмом памяти,
она не видела этого ограничения,
а видела общий объём памяти хоста.
Она сразу заводила большущий heap,
и ещё несколько раз его расширяла.
Редко запускала сборку мусора.
И довольно быстро упиралась в границы контейнера
и дохла с OutOfMemoryError
.
Тогда это чинилось флагом -XX:MaxRAM=256M
.
Мы просто говорим,
что памяти меньше.
И JRE спокойно ужимается до значительно меньшего объёма.
И работает.
Конечно,
обычные ограничения на размер heap,
тот же флаг -Xmx
,
тоже работает.
Но heap — это не единственная память,
нужная Java,
поэтому -XX:MaxRAM
для контейнера надёжнее.
Более свежие Java умеют видеть ограничения на память в контейнерах, которые в Linux задаются через cgroups. И такой проблемы нет.
Java не доставляет проблем. Я запускаю явы либо в ограниченных контейнерах, либо с ограниченным heap. Они не съедают всю память.
А всю память, как оказалось, часто съедает Chrome. Берёшь и перезапускаешь все хромовые вкладки, отдельные окна (c Gmail) и процессы. Полностью. И, хоба, снова почти такая свежая система с пустой памятью, как после перезагрузки.
Получается, что если приложение видит много памяти, оно старается её съесть. А можно ли Chrome сказать, что ему доступно немного памяти? С помощью тех же cgroups?
Cgroups — это control groups. Контрольные группы процессов. Чтобы за ними следить и управлять. Этот механизм ядра повсеместно используется всяческими разными менеджерами контейнеров под Linux, включая и Докер. Кому, если не ему, ограничивать разные пользовательские процессы?
Оказывается, МОЖНО.
Нужно поставить пакет cgroup-tools
,
скопировать несколько файлов конфигурации
из /usr/share/doc/cgroup-tools/examples/
в /etc/
и настроить запуск двух команд как системных сервисов.
Файл номер раз.
/etc/cgred.conf
.
# /etc/sysconfig/cgred.conf - CGroup Rules Engine Daemon configuration file
# The pathname to the configuration file for CGroup Rules Engine
CONFIG_FILE="/etc/cgrules.conf"
# Uncomment the following line to log to specified file instead of syslog
#LOG_FILE="/var/log/cgrulesengd.log"
# Uncomment the second line to run CGroup Rules Engine in non-daemon mode
NODAEMON=""
#NODAEMON="--nodaemon"
# Set owner of cgred socket. 'cgexec' tool should have write access there
# (either using suid and/or sgid permissions or Linux capabilities).
SOCKET_USER=""
SOCKET_GROUP="cgred"
# Uncomment the second line to disable logging for CGroup Rules Engine
# Uncomment the third line to enable more verbose logging.
LOG=""
#LOG="--nolog"
#LOG="-v"
Это конфигурация CGroup Rules Engine Daemon.
Это действительно демон cgrulesengd
,
который следит за процессами в системе
и засовывает их в группы согласно правилам.
Собственно,
это и есть способ навешать ограничения на процессы,
не меняя способ их запуска.
То есть как Хром запускали из главного меню,
так и будем запускать.
А демон уже его найдёт.
Второй файл.
/etc/cgconfig.conf
.
group chrome {
memory {
memory.limit_in_bytes = 4g;
}
}
Это объявление групп и ограничений в них.
За ограничениями следят контроллеры (controllers) в ядре.
Как минимум нам доступны cpu
и memory
.
Можно и ЦПУ прирезать.
Но пока сосредоточимся на памяти.
У memory
контроллера
много параметров.
Здесь мы просто ставим ограничение на доступную память.
Третий файл.
/etc/cgrules.conf
.
# /etc/cgrules.conf
#
# Example:
#<user> <controllers> <destination>
#@student cpu,memory usergroup/student/
#peter cpu test1/
#% memory test2/
gelin:/opt/google/chrome/chrome memory chrome
# End of file
Это правила для нашего демона. Как выявлять процессы. Надёжнее всего по имени пользователя и полному пути до исполняемого файла. Какие контроллеры применять. Какую группу назначать. Группы, как видите, могут быть вложенными.
Проверка.
Загрузить конфигурацию групп из файла командой
cgconfigparser
.
# /usr/sbin/cgconfigparser -l /etc/cgconfig.conf
Запустить нашего демона.
# /usr/sbin/cgrulesengd -vvv
В /sys/fs/cgroup/memory
должна появиться наша группа.
$ ls -1 --group-directories-first /sys/fs/cgroup/memory/ | head -5
chrome
docker
machine.slice
system.slice
user.slice
Внутри будут файлы со всеми ограничениями группы. Можно проверить, что ограничение на память стоит. Наши четыре гигабайта.
$ cat /sys/fs/cgroup/memory/chrome/memory.limit_in_bytes
4294967296
И самое главное. Нужно убедиться, что наш демон нашёл процессы Chrome.
$ cat /sys/fs/cgroup/memory/chrome/tasks | head -5
3682
3701
3702
3705
3709
Это PIDы всех процессов Chrome. Даже не процессов, а задач (tasks). Можете проверить.
Чтобы это всё осталось жить, нужно добавить пару Systemd Unit.
Инициализация групп при старте системы.
Файл /etc/systemd/system/cgconfigparser.service
.
[Unit]
Description=cgroup config parser
After=network.target
[Service]
User=root
Group=root
ExecStart=/usr/sbin/cgconfigparser -l /etc/cgconfig.conf
Type=oneshot
[Install]
WantedBy=multi-user.target
Запуск демона.
Файл /etc/systemd/system/cgrulesgend.service
.
[Unit]
Description=cgroup rules generator
After=network.target cgconfigparser.service
[Service]
User=root
Group=root
Type=forking
EnvironmentFile=-/etc/cgred.conf
ExecStart=/usr/sbin/cgrulesengd
Restart=on-failure
[Install]
WantedBy=multi-user.target
Не забудьте:
# systemctl daemon-reload
# systemctl enable cgconfigparser
# systemctl enable cgrulesgend
А эти cgroups действительно работают?
Chrome у нас порождает довольно много процессов. Как увидеть общий объём занимаемой ими памяти?
Большинство менеджеров процессов не умеют отображать данные
сразу по группе одноимённых процессов.
А вот atop
умеет.
Если его запустить, и нажать m
для показа информации о памяти,
а затем p
для суммирования по "program (i.e. same process name)"
(или просто запустить как atop -mp
),
то, в нижней половине экрана,
можно увидеть примерно это:
NPROCS SYSCPU USRCPU VSIZE RSIZE PSIZE SWAPSZ RDDSK WRDSK RNET SNET MEM CMD 1/10
36 0.10s 1.25s 162.3G 4.4G 0K 431.6M 0K 0K 0 0 28% chrome
4 0.80s 74.40s 17.2G 2.6G 0K 0K 0K 0K 0 0 17% java
5 0.02s 0.02s 4.2G 1.4G 0K 0K 0K 0K 0 0 9% upwork
1 0.04s 0.33s 3.3G 896.5M 0K 104K 0K 0K 0 0 6% latte-dock
4 0.00s 0.10s 25.1G 877.5M 0K 70704K 0K 0K 0 0 6% slack
8 0.00s 0.01s 15.8G 542.1M 0K 0K 0K 0K 0 0 3% jetbrains-tool
1 0.18s 0.41s 2.1G 484.9M 0K 40364K 0K 0K 0 0 3% Xorg
4 0.02s 0.53s 4.0G 478.6M 0K 0K 0K 0K 0 0 3% insomnia
1 0.05s 0.11s 2.5G 361.0M 0K 4248K 0K 0K 0 0 2% Telegram
Тут у нас присутствует аж несколько разных *SIZE
и *SZ
.
Что всё это значит?
VSIZE
— это объём виртуальной памяти процессов.
Виртуальная память — это виртуальное адресное пространство,
выделенное процессу.
Аж 162 гигабайта у Хрома, как видите.
Далеко не всё это пространство действительно представлено в физической памяти.
Что-то может уйти в свап.
А что-то само по себе является отображением файлов в память.
RSIZE
— это объём резидентной памяти процессов.
Также известен как resident set size (RSS).
Это физическая память, занятая процессом.
Сюда также включаются разделяемые библиотеки,
возможно, поэтому тут всё же больше 4 гигабайт.
PSIZE
— это proportional memory size.
Тут пытаются учесть, что разделяемые библиотеки используются несколькими процессами,
и поделить используемую ими память между ними.
По умолчанию atop этот параметр не считает,
потому что это сложно.
Но если добавите ключик -R
или нажмёте R
,
то окажется, что PSIZE
для Chrome раза в два меньше, чем RSIZE
.
Ох, непростое это дело, попытаться честно подсчитать использование памяти.
SWAPSZ
— это размер свапа процессов.
Хоть мы, вроде, и ограничили Хром в пожирании памяти,
в свап он всё равно лезет.
Ну и пусть.
Это, косвенно, подтверждает,
что ему тесновато, и ограничение по памяти работает.
Если что, в cgroup можно и свап прирезать.
Можно поступить попроще и пошаманить с ps
и grep
.
Вот только у ps
тоже много вариантов подсчитать размер памяти.
$ ps -eo rssize,size,vsize,command | grep chrome | head -5
404232 657240 1252856 /opt/google/chrome/chrome
17496 12376 271832 /opt/google/chrome/chrome --type=zygote --no-zygote-sandbox
20600 12376 271832 /opt/google/chrome/chrome --type=zygote
2012 2656 10792 /opt/google/chrome/nacl_helper
7820 12376 271832 /opt/google/chrome/chrome --type=zygote
rsssize
— это RSS, резидентная память.
vsize
— это виртуальная память.
size
— это "approximate amount of swap space
that would be required if the process were to dirty all writable pages and then be swapped out.
This number is very rough!"
Получается, это некий объём свапа, который понадобится процессу,
если его весь придётся срочно скинуть в свап и полностью освободить память.
Ну, как-то ps
это считает.
Я говорил, что это непросто?
Чтобы просуммировать по всем процессам Хрома,
пошаманим с awk
.
$ ps -eo rssize,size,vsize,command | grep chrome \
| awk '{ r=$1/1024/1024; s=$2/1024/1024; v=$3/1024/1024; sumr+=r; sums+=s; sumv+=v } END {print sumr, sums, sumv}'
4.5276 8.60119 163.155
Получились показания, совпадающие с выводом atop
(Хром немного шевелился между замерами).
А size
оказался раза в два больше RSS. 🤷
После недели экспериментов могу сказать, что, вроде, работает.
Хотя без atop
, в обычном системном мониторе ничего особо не видно.
Вся память chrome
размазана по куче мелких процессов.
На фоне присмиревшего Chrome видны другие пожиратели памяти. Например, клиент Upwork и мой любимый Latte-Dock. Чего этот док жрёт столько heapа, не знаю.
Надо попробовать отказаться от этого дока, раз он так память жрёт. Попробовал его так же через cgroup впихнуть в 256 мегабайт, он тормозил и сдох через часик. На 512 мегабайтах вроде шевелится.
А Chrome на четырёх гигабайтах работает вроде нормально. Тормозов или неудобств не ощущаю. Но CPU по-прежнему готов весь съесть. Если Google Meet работает, не пытайтесь открыть Google Docs, будет грузить документ пару минут.
Есть ещё, что оптимизировать.