🧠
Memória: stack, heap, virtual memory, page fault
⏱ 16 min de leitura·+80 XP
Todo processo "acredita" que tem acesso a gigabytes de memória — mas a maioria não existe fisicamente. Virtual memory é a ilusão que o SO mantém para cada processo, permitindo isolamento, overcommit e mapeamento de arquivos sem copiar dados.
Stack vs Heap: dois modelos de alocação
| Aspecto | Stack | Heap |
|---|---|---|
| Alocação | Automática (LIFO) | Explícita (malloc/new/alloc) |
| Liberação | Automática (ao retornar da função) | Explícita ou GC |
| Velocidade | O(1) — apenas mover stack pointer | O(log n) para encontrar bloco livre |
| Tamanho | Limitado (~8MB Linux default) | Limitado pela RAM + swap |
| Fragmentação | Nenhuma | Pode fragmentar com muitos alloc/free |
| Thread | Uma stack por thread | Heap compartilhado (com lock) |
| Em Python | Call stack do interpretador | Todos os objetos Python vivem aqui |
# Stack overflow: recursão infinita ou stack frame muito grande
import sys
print(sys.getrecursionlimit()) # 1000 por default
def recursao_infinita(n):
return recursao_infinita(n + 1) # RecursionError após ~1000 calls
# Aumentar limite com cuidado (cada frame ocupa ~1-4KB na stack):
sys.setrecursionlimit(10_000) # 10MB de stack frames
# Ver tamanho da stack em bytes (Unix):
import resource
stack_size = resource.getrlimit(resource.RLIMIT_STACK)
# (8388608, -1) = 8MB soft limit, unlimited hard limit
# Em Python, objetos vivem no heap
# sys.getsizeof() mostra o tamanho do objeto Python no heap:
import sys
print(sys.getsizeof([])) # 56 bytes (lista vazia)
print(sys.getsizeof([1, 2, 3])) # 88 bytes (3 ponteiros + overhead)
print(sys.getsizeof("hello")) # 54 bytes
print(sys.getsizeof(42)) # 28 bytes (int Python é um objeto!)Virtual Memory: o layout do espaço de endereçamento
# Layout do espaço de endereçamento virtual de um processo Linux (64-bit):
#
# 0xFFFFFFFFFFFFFFFF ← endereço mais alto
# ┌─────────────────┐
# │ Kernel space │ ← inacessível ao userspace (protegido pelo MMU)
# ├─────────────────┤ 0x7FFFFFFFFFFFFFFF
# │ Stack │ ← cresce para baixo (LIFO)
# │ ↓ │
# ├─────────────────┤
# │ (vazio) │ ← espaço não mapeado — qualquer acesso = segfault
# ├─────────────────┤
# │ ↑ │
# │ Heap │ ← cresce para cima (malloc/mmap)
# ├─────────────────┤
# │ BSS / Data │ ← variáveis globais inicializadas / zero
# ├─────────────────┤
# │ Text (código) │ ← executável mapeado aqui (read-only)
# └─────────────────┘ 0x0000000000000000
# Inspecionar mapeamento de memória de um processo:
import os
pid = os.getpid()
# cat /proc/{pid}/maps mostra todos os mapeamentos virtuais
# mmap: mapear arquivo diretamente no espaço de endereçamento
import mmap
with open("dados.bin", "rb") as f:
# Mapeia o arquivo — não copia para RAM imediatamente
# Página só é carregada quando acessada (demand paging)
mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
dados = mm[1000:2000] # lê bytes 1000-2000 (pode disparar page fault)
mm.close()
# Shared memory entre processos (sem serialização):
from multiprocessing import shared_memory
shm = shared_memory.SharedMemory(create=True, size=1024)
# Múltiplos processos podem mapear o mesmo shm — dados compartilhados sem IPCPage faults e swap: quando a memória acaba
# Monitorar page faults de um processo
import resource
import time
# getrusage retorna estatísticas de uso de recursos
uso_antes = resource.getrusage(resource.RUSAGE_SELF)
# ... código que aloca e acessa memória ...
dados = bytearray(100 * 1024 * 1024) # 100MB
for i in range(0, len(dados), 4096):
dados[i] = 1 # acessa cada página (4KB)
uso_depois = resource.getrusage(resource.RUSAGE_SELF)
minor_faults = uso_depois.ru_minflt - uso_antes.ru_minflt
major_faults = uso_depois.ru_majflt - uso_antes.ru_majflt
print(f"Minor page faults: {minor_faults}") # ~25600 (100MB / 4KB)
print(f"Major page faults: {major_faults}") # 0 (dados não foram para swap)
# Verificar uso de memória do processo atual
def memoria_atual_mb() -> float:
with open(f"/proc/{os.getpid()}/status") as f:
for linha in f:
if linha.startswith("VmRSS:"):
return int(linha.split()[1]) / 1024
print(f"RSS: {memoria_atual_mb():.1f} MB")
# Swap: quando RAM está cheia, o kernel move páginas para o disco
# /proc/swaps mostra partições de swap em uso
# Verificar swap:
import subprocess
resultado = subprocess.run(["free", "-h"], capture_output=True, text=True)
print(resultado.stdout)
# Mem: 15.5Gi 12.1Gi 1.2Gi ...
# Swap: 2.0Gi 0B 2.0Gi ← swap não está sendo usado✅
Modelo mental: processos operam em endereços virtuais — a MMU traduz para físico. Stack é rápida e automática mas limitada (~8MB). Heap é flexível mas requer gerenciamento. Virtual memory permite isolamento entre processos e mapeamento lazy de arquivos. Major page faults (swap) são caros — monitor com
vmstat e sar em produção.💡
Próximo: Syscalls — a fronteira entre código de aplicação e o kernel que gerencia hardware.
🧩
Quiz rápido
3 perguntas · Acerte tudo e ganhe o badge 🎯 Gabarito
Continue lendo