🧠FFVAcademy
🧠

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

AspectoStackHeap
AlocaçãoAutomática (LIFO)Explícita (malloc/new/alloc)
LiberaçãoAutomática (ao retornar da função)Explícita ou GC
VelocidadeO(1) — apenas mover stack pointerO(log n) para encontrar bloco livre
TamanhoLimitado (~8MB Linux default)Limitado pela RAM + swap
FragmentaçãoNenhumaPode fragmentar com muitos alloc/free
ThreadUma stack por threadHeap compartilhado (com lock)
Em PythonCall stack do interpretadorTodos 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 IPC

Page 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