W3docs

Dicas de Performance em Java

Dicas práticas de performance em Java — meça antes, evite otimização prematura e micro-otimizações comuns.

Java é rápido o suficiente para quase tudo, mas código lento ainda acontece — geralmente por fazer trabalho demais, alocar memória em excesso ou escolher a estrutura de dados errada. A dica de performance mais importante não é um truque: meça antes de mudar qualquer coisa. Este capítulo explica como medir com honestidade, as otimizações que mais valem a pena (construção de strings, escolha de estruturas de dados, evitar alocações desnecessárias) e as armadilhas que tornam código "rápido" ingênuo na verdade lento.

Meça primeiro, otimize depois

Adivinhar sobre performance é como se gasta horas acelerando código que nunca foi lento. Perfilie uma carga de trabalho real, encontre o caminho crítico (a pequena fração do código onde a maior parte do tempo é gasta) e só então otimize. Para experimentos rápidos, System.nanoTime() fornece um delta de relógio; para benchmarks sérios, use uma ferramenta como o JMH (Java Microbenchmark Harness), que aquece o JIT e leva em conta o ruído de medição.

long start = System.nanoTime();
doWork();
long elapsedMs = (System.nanoTime() - start) / 1_000_000;
System.out.println("Took " + elapsedMs + " ms");

Duas regras acompanham isso. Primeiro, evite otimização prematura — código claro que é rápido o suficiente supera código inteligente que é difícil de ler. Segundo, o compilador JIT da JVM otimiza código quente em tempo de execução, então um método só atinge velocidade total após ser executado muitas vezes; uma única chamada cronometrada diz pouco.

Construa strings com StringBuilder

Como strings são imutáveis, s += x dentro de um loop cria uma string completamente nova e copia todos os caracteres anteriores a cada iteração — isso é trabalho O(n²). O StringBuilder mantém um buffer expansível e anexa no lugar, transformando o mesmo trabalho em O(n).

// Slow: a new String allocated every iteration
String csv = "";
for (String field : fields) {
    csv += field + ",";
}

// Fast: one buffer, appended in place
StringBuilder sb = new StringBuilder();
for (String field : fields) {
    sb.append(field).append(',');
}
String csv2 = sb.toString();

Um único a + b + c fora de um loop é aceitável — o compilador já o transforma em um único StringBuilder (veja concatenação de strings para o que o compilador faz). O problema é a concatenação dentro de um loop, onde cada passagem adiciona mais uma cópia completa.

Escolha a estrutura de dados certa

Os maiores ganhos geralmente vêm de escolhas algorítmicas, não de micro-ajustes. Buscar um valor em um ArrayList percorre cada elemento (O(n)); um HashMap ou HashSet faz isso em tempo aproximadamente constante (O(1)). Escolha a coleção que corresponde à forma como você realmente acessa os dados.

NecessidadeUseCusto de busca
Acesso por índice, anexar no finalArrayListO(1) por índice, O(n) por valor
Busca por chave/valorHashMapO(1) médio
Teste de pertinência, sem duplicatasHashSetO(1) médio
Chaves ordenadasTreeMapO(log n)
Inserção/remoção frequente nas extremidadesArrayDequeO(1) nas extremidades

Se você conhece o tamanho final, passe-o ao construtor: new ArrayList<>(10_000) ou new HashMap<>(capacity). Isso evita a realocação e cópia repetidas que ocorrem à medida que uma coleção cresce.

Evite criação desnecessária de objetos

Todo objeto alocado deve ser coletado depois, e a coleta de lixo não é gratuita. Reutilize valores imutáveis, prefira primitivos em vez de seus wrappers em loops intensivos e não crie objetos que você descarta imediatamente.

// Autoboxing: every += boxes a new Integer
Long total = 0L;
for (int i = 0; i < n; i++) total += i;   // slow, allocates boxes

// Primitive: no allocation at all
long sum = 0L;
for (int i = 0; i < n; i++) sum += i;     // fast

Outros ganhos fáceis: armazene em cache objetos Pattern compilados em vez de chamar String.matches() em um loop, reutilize um DateTimeFormatter (é thread-safe e imutável) e prefira for aprimorado em vez de streams nos loops internos mais intensivos onde a alocação importa.

java— editable, runs on the server

O que tirar da execução:

  • Same result? true prova que StringBuilder produz exatamente a mesma string que +=, então substituí-lo muda apenas a velocidade, nunca a correção.
  • A razão "x slower" impressa mostra que a concatenação em loop custa muito mais do que anexar a um buffer, porque cada += copia toda a string até então.
  • Both lists size 100000: true confirma que um ArrayList pré-dimensionado termina idêntico a um que cresceu — a dica do construtor afeta a alocação, não o conteúdo.
  • Pre-sized faster? true mostra que informar ao ArrayList sua capacidade antecipadamente evita as etapas repetidas de redimensionamento e cópia.
  • Map lookups found: 50000 in ... ms demonstra que 50.000 buscas em HashMap terminam em aproximadamente um milissegundo, o retorno de escolher acesso O(1) em vez de uma varredura de lista O(n).

Armadilhas comuns

Alguns erros transformam código "obviamente mais rápido" no oposto:

  • Confiar em uma única execução cronometrada. O JIT ainda não aqueceu, e o SO pode ter agendado outra coisa no meio da medição. Repita o trabalho milhares de vezes, ou use JMH, antes de acreditar em um número.
  • Micro-otimizar código frio. Um método que executa uma vez na inicialização não ganha nada com um loop mais compacto. Invista esforço apenas no caminho crítico que o profiler aponta.
  • Construção de strings com + em um loop. A desaceleração evitável mais comum — use StringBuilder sempre que concatenar dentro de um loop.
  • Autoboxing oculto. Um List<Integer>, Map<Integer, Integer> ou um acumulador Long perdido embala cada valor. Em um loop numérico intensivo, prefira primitivos e arrays de primitivos.
  • Otimizar antes de funcionar. Correto primeiro, rápido depois. Código claro que você pode perfilar supera código inteligente que você não consegue raciocinar.

Resumo

  • Meça antes de mudar qualquer coisa — perfilie uma carga de trabalho real e otimize apenas o caminho crítico.
  • Construa strings com StringBuilder, não com +=, dentro de loops.
  • Combine a coleção com o padrão de acesso: HashMap/HashSet para buscas, ArrayList para acesso indexado; pré-dimensione quando o tamanho for conhecido.
  • Evite alocação desnecessária: prefira primitivos, reutilize objetos imutáveis e lembre-se de que cada objeto adiciona trabalho de coleta de lixo.

Prática

Prática
Por que usar '+=' para construir uma String dentro de um loop é lento em comparação com StringBuilder?
Por que usar '+=' para construir uma String dentro de um loop é lento em comparação com StringBuilder?
Was this page helpful?