Quando executamos um programa, o sistema operacional aloca memória para que essa aplicação possa armazenar dados e recuperá-los pelo tempo que for necessário. Diferentemente de C ou C++, em que o programador tem a liberdade de fazer a alocação de memória e gerenciar ponteiros "na mão", em Java quem faz a gestão dos armazenamentos voláteis é a JVM (Java Virtual Machine), abstraindo essa complexidade para nós em dois sistemas de alocação, a Heap e a Stack.
A diferença entre elas está no objetivo do armazenamento e na necessidade (ou não) de compartilhar aquele dado com múltiplos processos.
A JVM utiliza a Stack para dados de curto prazo e segue uma estrutura LIFO (Last In First Out - último a chegar, primeiro a sair). A Stack é uma área de memória utilizada para gerenciar a execução de métodos, armazenar variáveis locais e chamadas de método de forma organizada em frames. Cada thread na JVM tem sua própria stack.
Já a Heap é uma área de memória global compartilhada entre todas as threads, onde são alocados objetos e variáveis que precisam ter uma vida útil além do escopo do método que os criou.
A memória na Heap é gerenciada pelo Garbage Collector, que remove objetos não referenciados para liberar espaço, sendo usada para dados de longo prazo.
Vamos usar um exemplo prático.
Quando uma variável primitiva é criada no escopo de um método, ela deve ficar disponível para uso enquanto o método for executado. Desta forma, é a Stack quem guardará essa variável, pois ela é responsável pelo ciclo de vida dos dados que possuem uma utilidade única e específica no programa.
Porém, quando um método cria uma instância, esse objeto poderá ser utilizado em outros trechos do programa, e não apenas onde foi declarado. Isso acontece claramente quando criamos um objeto que representa um registro no banco de dados. No nosso programa, essa mesma instância pode ser consultada, editada e removida ao longo de uma execução. Desta forma, quem cuidará do armazenamento do objeto será a Heap.
Para ilustrar essa questão, vou utilizar um exemplo simples que o autor Hanumant Deshmukh descreve em seu guia "OCP Java SE 17/21 Programmer Exam Fundamentals". Esse livro, aliás, é maravilhoso, pois consegue ser bem didático na explicação de processos bastante complexos. Super recomendo se você, como eu, está em busca da certificação Oracle Certified Professional (OCP). O autor usa uma instância de String para fins didáticos, mas vou usar um objeto customizado aqui só para não correr o risco de burlar direitos autorais (:S)
public class Main {
public static void main(String[] args) {
HeapObj heapObject = newObject();
int counter = 0;
while (counter++ < 10){
print(heapObject.getName());
}
}
public static HeapObj newObject(){
return new HeapObj("Happy instance");
}
public static void print(String text){
System.out.println(text);
}
}
No exemplo, a classe Main chama três métodos: o principal (main), um que cria a instância do objeto HeapObj, e outro que apenas imprime um texto.
Se tirássemos uma fotografia quando todos os métodos já foram chamados, a Stack e a Heap estariam assim:
Ou seja:
1. Na inicialização:
Stack: main frame (contém args, heapObject, counter).
Heap: Vazia.
2. Após newObject:
Stack: main frame (contém args, heapObject referência, counter).
Heap: Um objeto HeapObj com a string "Happy instance".
3. Durante o Loop:
Stack: main frame (contém args, heapObject referência, counter), múltiplos frames print que são empilhados e desempilhados.
Heap: O mesmo objeto HeapObj e a string "Happy instance".
4. Após o loop:
Stack: main frame (contém args, heapObject referência).
Heap: O mesmo objeto HeapObj e a string "Happy instance".
5. Ao final do programa:
Stack: vazia.
Heap: vazia.
Top comments (0)