Embora esses dois tipos de memórias se refiram a formas diferentes de alocar e gerenciar dados durante a execução de um programa, entender como funcionam ajuda a prever o desempenho e possíveis comportamentos inesperados do código em desenvolvimento.
O compilador tem acesso direto à stack memory, já que ela é usada para armazenar variáveis locais e dados cujo o tamanho é conhecido em tempo de compilação (significa que o compilador sabe exatamente quantos bytes aquele dado vai ocupar antes do programa começar a rodar, ou seja, o tamanho da variável é fixa e previsível), o que garante maior eficiência.
Por outro lado, a heap memory é utilizada para armazenar dados que precisam permanecer acessíveis além do escopo atual ou cujo o tamanho só é conhecido em tempo de execução. Nela ficam os valores "reais" apontados por variáveis de referência, como objetos, arrays e estruturas dinâmicas.
Stack Memory
A stack memory é responsável por armazenar tipos por valor. Mas o que são tipos por valor?
Tipos por valor são aqueles em que a variável guarda diretamente o valor em si, e não uma referência para ele.
Exemplos comuns de tipos por valor: number
, boolean
, char
, struct
, int
, float
.
let a = 10;
let b = a;
b = 20;
console.log(a); // 10 => não foi afetado
Mas stack memory, também é uma estrutura de dados LIFO (Last in, First out), onde armazena dados de curta duração, como:
- Chamadas de funções (frames da stack)
- Variáveis primitivas (valores por valor, como mencionado anteriormente)
- Referências para objetos que estão na heap
Algumas características são:
- Rápida, alocação e desalocação ocorrem automaticamente, ajustando apenas o stack pointer.
- Limitada, espaço pequeno (em torno de ~1MB por thread no Node.js por exemplo, podendo variar por sistema).
- Limpeza automática quando uma função termina (os frames são removidos da pilha).
- Armazena variáveis locais, parâmetros de função e endereços de retorno.
- Quando a pilha antige seu limite, ocorre o erro conhecido como "stack overflow".
Heap Memory
Já a heap memory é responsável por armazenar tipos por referência.
Tipos por referência são aqueles em que a variável guarda o endereço (referência) do valor na memória, e não o valor em sí.
Quando uma variável é copiada, ambas passam a apontar para o mesmo dado, e alterar uma afeta a outra.
Tipos dos dados mais comuns utilizados por referência são: object
, array
, function
(em JS), classes ou ponteiros.
let a = { x: 10 };
let b = a;
b.x = 20;
console.log(a.x); // 20 => ambos apontam para o mesmo objeto
A heap é uma região da memória usada para armazenar dados dinâmicos, cujo o tamanho ou tempo de vida não são conhecidos em tempo de compilação. Nela ficam os valores reais referenciados pelas variáveis da stack.
Algumas características:
- Armazena objetos, arrays e funções.
- Permite alocação dinâmica de memória.
- É mais lenta que a stack, pois envolve gerenciamento de memória e ponteiros.
- É gerenciada automaticamente por um Garbage Collector (em linguagens como JavaScript, Java, Go)
- Os dados permanecem na memória até não serem mais referenciados.
E quando um dado deixa de ser referencido?
Um dado (objeto
, array
ou função
) deixa de ser referenciado quando nenhuma variável ou estrutura ativa do programa aponta mais para ele na memória.
Em outras palavras: não existe mais nenhum caminho, a partir do código em execução, que leve até aquele valor na heap.
Quando isso acontece, o garbage collector entende que o valor não pode mais ser acessado e libera a memória automaticamente, um exemplo bem básico:
let user = { name: 'John'}
// A variável user referencia o objeto na heap
user = null
// Agora não existe mais nenhuma referência para o objeto { name: 'John' }
O objeto { name: 'John' } ainda existe fisicamente na heap mas como nenhuma variável aponta para ele, o garbage collector irá removê-lo em algum momento. Agora um outro exemplo interessante de perda de referência:
let obj1 = { x: 10 };
let obj2 = obj1;
obj1 = null;
// Ainda há uma referência ativa (obj2), então o dado continua na heap
obj2 = null;
// Agora não há mais nenhuma — o dado é elegível para coleta
Stack + Heap em conjunto (para assimilar mais rápido)
Durante a execução de um programa, a stack e a heap trabalham lado a lado par armazenar e gerenciar dados de forma eficiente.
A stack guarda as referências e valores simples (por valor), enquanto a heap armazena os valores complexos (por referência)
Quando uma variável na stack aponta para um objeto ou array, o endereço de memória desse objeto fica salvo na stack, mas o conteúdo real está na heap (como foi explicado anteriormente), exemplo:
function createUser() {
const user = { name: "Henrique", age: 25 };
return user;
}
const newUser = createUser();
- A função
createUser
cria uma variável user na stack. - Essa variável guarda uma referência para um objeto armazenado na heap.
- Quando a função termina, a stack é liberada, mas o objeto na heap permanece se ainda estiver sendo referenciado (
newUser
aponta para ele por exemplo).
Blank Space
Blank space é um espaço entre a stack memory e a heap memory, que é utilizada para expansão de uma das memórias caso haja necessidade (é interessante saber que existe caso queiram se aprofundar mais sobre o assunto).
Espero que esta explicação tenha ajudado a entender de forma prática o papel da stack e da heap, e como eles impactam a performance e o funcionamento do código. Ainda há muito a explorar sobre esse tema, como o funcionamento do Garbage Collector, otimizações de memória e outros detalhes que merecem posts e estudos dedicados da minha parte. Para quem quiser se aprofundar, certamente vale a pena.
Top comments (0)