Можно ли выделить процессам памяти больше, чем физической в системе
Что проверяют
Понимаете ли вы, что процесс получает виртуальное адресное пространство, а физические страницы появляются позже. Для Python-разработчика следом идёт практика: почему сервис убил OOM killer без всякого MemoryError и почему контейнер завершился с кодом 137.
Как отвечать
Да, можно, и Linux делает это по умолчанию. Механизмов несколько:
- виртуальная память:
mallocиmmapрезервируют адреса, а физическая страница (обычно 4 КиБ на x86) выделяется при первой записи через page fault. Отсюда разница между VSZ и RSS вpsиpsutil; - overcommit: параметр
vm.overcommit_memory. Значение0(по умолчанию) отказывает только в явно невыполнимых запросах,1разрешает всё,2включает строгий учёт, где предел считается от swap иvm.overcommit_ratio; - swap: редко используемые страницы уходят на диск. При активной подкачке задержки растут на порядки;
- отображение файлов: модуль
mmapиnumpy.memmapпозволяют работать с файлом больше ОЗУ, система подгружает только нужные страницы.
Когда заканчиваются и ОЗУ, и swap, приходит OOM killer и завершает процесс с наибольшим oom_score, в dmesg остаётся запись Out of memory: Killed process. В контейнере предел задаёт cgroup (memory.max в cgroup v2, --memory в Docker, limits в Kubernetes), и превышение даёт OOMKilled с кодом 137.
Пример на Python: массив np.zeros на несколько гигабайт создаётся мгновенно и почти не увеличивает RSS, потому что нулевые страницы не заняты, пока в них не пишут. MemoryError вы получите, только если аллокатор вернул ошибку, например при vm.overcommit_memory=2 или лимите через resource.setrlimit(resource.RLIMIT_AS, ...).
Где спотыкаются
Рассчитывают поймать нехватку памяти через except MemoryError. При стандартном overcommit процесс просто получает SIGKILL, и обработчик не выполнится.
Пугаются большого VSZ. Виртуальный размер с потоками и отображёнными библиотеками мало говорит о реальном потреблении.
Забывают про copy-on-write после fork в multiprocessing. CPython пишет счётчик ссылок в каждый объект, и страницы родителя копируются в дочерние процессы. Частично помогает gc.freeze() перед созданием пула.