Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Out of Memory в Python
Out of Memory (OOM) — это состояние, когда приложение исчерпывает доступную оперативную память (RAM), и операционная система больше не может выделять дополнительную память для процесса. В Python это приводит к критическому отказу программы.
Когда происходит Out of Memory
OOM killer в Linux — процесс, который убивает приложения, когда система исчерпывает доступную память:
# Плохой пример — бесконечное выделение памяти
big_list = []
while True:
big_list.append(list(range(1000000))) # Добавляем по 8MB каждый раз
# ОС убьёт этот процесс с сигналом SIGKILL
Типичные причины OOM
1. Утечки памяти (Memory Leaks):
# Глобальный список, который никогда не очищается
cache = []
def process_request(data):
cache.append(data) # Память растёт с каждым запросом
return len(cache)
# Если это веб-приложение с миллионами запросов
# memory будет расти бесконечно
2. Большие структуры данных в памяти:
import sys
# Загрузка огромного файла в память целиком
with open("huge_file.csv", "r") as f:
all_data = f.readlines() # Весь файл (например, 100GB) в памяти
print(f"Size: {sys.getsizeof(all_data) / (1024**3)} GB")
3. Циклические ссылки без очистки:
class Node:
def __init__(self):
self.ref = None
root = Node()
root.ref = root # Циклическая ссылка
# Объект не удаляется, пока не сработает garbage collector
del root # Память может остаться занята временно
4. Множество объектов в памяти:
# Создание миллионов объектов без удаления
users = []
for i in range(100_000_000):
users.append({"id": i, "data": "x" * 1000})
# Каждый словарь занимает память
# ~1GB использовано за несколько минут
Диагностика проблем с памятью
1. Мониторинг с использованием psutil:
import psutil
import os
process = psutil.Process(os.getpid())
mem_info = process.memory_info()
print(f"RSS (physical RAM): {mem_info.rss / (1024**2):.2f} MB")
print(f"VMS (virtual memory): {mem_info.vms / (1024**2):.2f} MB")
# Также можно отслеживать использование в процессе выполнения
for i in range(5):
process.memory_percent() # % от общей памяти
print(f"Memory: {process.memory_info().rss / (1024**3):.2f} GB")
2. Профилирование с memory_profiler:
# Установка: pip install memory-profiler
# @profile декоратор показывает использование памяти для каждой строки
from memory_profiler import profile
@profile
def my_function():
a = [i for i in range(1000000)]
b = [i * 2 for i in a]
return sum(b)
my_function()
Вывод:
Line # Mem usage Increment Line Contents
5 38.5 MiB 0.0 MiB def my_function():
6 46.2 MiB 7.7 MiB a = [i for i in range(1000000)]
7 53.9 MiB 7.7 MiB b = [i * 2 for i in a]
8 53.9 MiB 0.0 MiB return sum(b)
3. Анализ объектов с objgraph:
import objgraph
# Показать самые частые типы объектов
objgraph.show_most_common_types(limit=10)
# Отслеживать утечки
objgraph.show_growth(limit=3)
# Найти утечки между снимками
objgraph.show_refs([obj], max_depth=3)
Стратегии предотвращения OOM
1. Обработка данных потоком (Streaming):
# Плохо — весь файл в памяти
with open("large_file.csv", "r") as f:
data = f.readlines()
# Хорошо — обработка строка за строкой
with open("large_file.csv", "r") as f:
for line in f: # Итератор, не загружает весь файл
process_line(line)
2. Использование генераторов вместо списков:
# Плохо — создаёт весь список в памяти
def get_numbers():
return [i for i in range(1_000_000_000)] # Огромный список
# Хорошо — генератор, ленивые вычисления
def get_numbers():
for i in range(1_000_000_000):
yield i # Возвращает по одному значению
for num in get_numbers():
print(num) # Каждое значение создаётся на лету
3. Явная очистка памяти:
import gc
large_list = list(range(10_000_000))
print(f"Memory before: {process.memory_info().rss / (1024**2):.2f} MB")
# Очистка переменной
del large_list
# Принудительная очистка мусора
gc.collect()
print(f"Memory after: {process.memory_info().rss / (1024**2):.2f} MB")
4. Ограничение размера кэша:
from functools import lru_cache
# maxsize ограничивает размер кэша
@lru_cache(maxsize=128)
def expensive_function(n):
return n ** 2
# Для больших наборов данных:
from collections import OrderedDict
class LimitedCache:
def __init__(self, max_size=1000):
self.cache = OrderedDict()
self.max_size = max_size
def set(self, key, value):
if key in self.cache:
del self.cache[key] # Переместить в конец
self.cache[key] = value
if len(self.cache) > self.max_size:
self.cache.popitem(last=False) # Удалить самый старый
5. Использование контекстных менеджеров:
# Файл автоматически закроется и освободит ресурсы
with open("file.txt", "r") as f:
data = f.read()
# f уже закрыт, память освобождена
# Классический способ (неправильный)
f = open("file.txt", "r")
data = f.read()
# f остаётся открытым, пока не будет сборка мусора
Что происходит при OOM в Docker и Kubernetes
# Контейнер может быть убит OOM killer
# В Kubernetes подобные контейнеры перезагружаются
import os
import psutil
def monitor_memory():
# Получить лимиты контейнера
with open("/sys/fs/cgroup/memory/memory.limit_in_bytes") as f:
limit = int(f.read())
current_memory = psutil.virtual_memory().used
percent = (current_memory / limit) * 100
if percent > 90:
print(f"WARNING: {percent:.1f}% of container memory used")
Практические советы
- Используйте генераторы для больших наборов данных
- Профилируйте память перед production
- Устанавливайте лимиты памяти в контейнерах
- Мониторьте использование памяти в production
- Избегайте циклических ссылок и утечек
- Используйте слабые ссылки (weakref) если необходимо
Out of Memory — это критическое состояние, которое требует профилактики, мониторинга и правильного дизайна приложения для предотвращения потери данных и сбоев в production.