Introdução: por que perfilar código Java?
\n\nDesenvolvimento em Java não é apenas escrever código correto, mas também criar aplicações rápidas e escaláveis. Mesmo um sistema perfeitamente projetado pode ficar lento devido a uma única linha não otimizada. Como encontrar esse "gargalo"? A resposta é a perfilagem.
\n\nPerfilagem é o processo de coleta de dados sobre a operação de uma aplicação: tempo de execução de métodos, uso de memória, threads e carga da CPU. Sem ela, a otimização se torna um palpite. Neste artigo, vamos analisar exemplos práticos de perfilagem de código Java, aprender a interpretar os resultados e encontrar gargalos.
\n\nVamos considerar duas ferramentas populares: VisualVM (gratuita, integrada ao JDK) e JProfiler (comercial, mas poderosa). Todos os exemplos serão com código real.
\n\nParte principal: prática de perfilagem
\n\n1. Configuração do ambiente e inicialização do profiler
\n\nPara começar, certifique-se de ter o JDK instalado (8 ou superior). O VisualVM acompanha o JDK 8 e 9; para versões mais recentes, é necessário baixá-lo separadamente do site oficial.
\n\nVamos escrever um código "lento" simples para teste. Crie a classe SlowApp.java:
import java.util.ArrayList;\nimport java.util.List;\nimport java.util.Random;\n\npublic class SlowApp {\n public static void main(String[] args) throws InterruptedException {\n List<String> data = new ArrayList<>();\n Random random = new Random();\n\n // Simulação de carregamento lento de dados\n for (int i = 0; i < 100_000; i++) {\n data.add(generateString(random));\n }\n\n // Processamento de dados\n long sum = 0;\n for (String s : data) {\n sum += processString(s);\n }\n\n System.out.println("Result: " + sum);\n Thread.sleep(10000); // Para ter tempo de conectar o profiler\n }\n\n private static String generateString(Random random) {\n StringBuilder sb = new StringBuilder(100);\n for (int i = 0; i < 100; i++) {\n sb.append((char) ('a' + random.nextInt(26)));\n }\n return sb.toString();\n }\n\n private static long processString(String s) {\n long result = 0;\n for (char c : s.toCharArray()) {\n result += c;\n // Atraso artificial\n for (int j = 0; j < 100; j++) {\n result += Math.sqrt(j);\n }\n }\n return result;\n }\n}\n\nExecute a aplicação:
\njavac SlowApp.java\njava SlowApp\n\nEnquanto o programa estiver em execução (ele dormirá por 10 segundos), abra o VisualVM. No painel esquerdo, encontre seu processo (SlowApp) e clique duas vezes. Vá para a aba Sampler ou Profiler — você verá estatísticas de CPU e memória.
\n\n2. Análise de CPU: encontrando o método mais pesado
\n\nNo VisualVM, clique no botão CPU na seção Sampler. Após alguns segundos, você obterá uma tabela com métodos ordenados por tempo de execução (Self Time).
\n\nEm nosso exemplo, você verá que o método processString ocupa mais de 80% do tempo. Dentro dele, o loop aninhado Math.sqrt(j). Este é o gargalo.
Como interpretar isso?
\n- \n
- Self Time — tempo gasto diretamente na execução do método (excluindo chamadas de métodos filhos). \n
- Total Time — tempo total, incluindo todas as chamadas aninhadas. \n
- Invocations — número de chamadas do método. \n
Exemplo de saída (simplificada):
\nMethod Self Time (ms) Invocations\nSlowApp.processString 8500 100000\nSlowApp.generateString 1200 100000\njava.lang.Math.sqrt 6000