Java para programação de sistemas: quando a linguagem "pesada" se torna uma ferramenta leve
\n\nQuando se ouve "programação de sistemas", geralmente vêm à mente associações com C, assembly, ponteiros nus e gerenciamento manual de memória. Java é a última coisa que vem à mente. E é um erro. Ao longo de mais de vinte anos de evolução, Java passou de "monstro lento para sites corporativos" a uma ferramenta completa para escrever utilitários de sistema, daemons de rede, monitoramento de recursos e até componentes de sistemas operacionais.
\n\nClaro, Java não substituirá C na escrita de um driver de placa de vídeo. Mas para 90% das tarefas de programação de sistemas — trabalho com processos, descritores de arquivo, sockets de rede, gerenciamento de memória em nível de SO — Java oferece uma abordagem elegante, segura e multiplataforma. Neste artigo, analisaremos exemplos reais de como usar Java para programação de sistemas e mostraremos que o código em Java pode ser tão eficiente quanto em C++, mas muito mais legível.
\n\nPor que Java não é apenas "enterprise"?
\n\nO principal estereótipo: Java é lento por causa da coleta de lixo e da máquina virtual. Isso era verdade em 2004. Hoje, a JVM usa compilação para código nativo (JIT), previsão de desvios e coleta de lixo geracional com pausas de menos de um milissegundo. Para programação de sistemas, três coisas são críticas:
\n\n- \n
- Multiplataforma — um utilitário funciona em Linux, Windows e macOS sem recompilação. \n
- Segurança de memória — sem segfault, sem vazamentos de memória (no sentido clássico). \n
- Biblioteca padrão rica — trabalho com processos, NIO, sockets, canais de arquivo — tudo embutido. \n
São essas qualidades que tornam Java uma excelente escolha para escrever utilitários de sistema que devem funcionar de forma estável e previsível.
\n\nTrabalho com processos: ProcessBuilder e Runtime.exec
\n\nQualquer utilitário de sistema, mais cedo ou mais tarde, executa programas externos. Em Java, isso é feito por Runtime.exec() e pelo mais flexível ProcessBuilder. O segundo é preferível: ele permite gerenciar o ambiente, o diretório de trabalho e redirecionar os fluxos de entrada/saída.
Exemplo: monitoramento do uso de memória (Linux)
\n\nSuponha que queremos obter a porcentagem de RAM usada. Em vez de analisar /proc/meminfo manualmente, podemos executar free e processar a saída:
import java.io.*;\n\npublic class MemMonitor {\n public static void main(String[] args) throws IOException {\n ProcessBuilder pb = new ProcessBuilder("free", "-m");\n pb.redirectErrorStream(true);\n Process process = pb.start();\n\n StringBuilder output = new StringBuilder();\n try (BufferedReader reader = new BufferedReader(\n new InputStreamReader(process.getInputStream()))) {\n String line;\n while ((line = reader.readLine()) != null) {\n output.append(line).append("\\n");\n }\n }\n\n int exitCode = process.waitFor();\n if (exitCode == 0) {\n System.out.println("Memória (MB):");\n System.out.println(output.toString());\n } else {\n System.err.println("Erro ao chamar free");\n }\n }\n}\n\nEste código é um exemplo típico de programação de sistemas em Java: executar um processo nativo, ler sua saída, tratar erros. Importante: sempre use redirectErrorStream(true) para evitar deadlock ao ler dois fluxos (stdout e stderr) simultaneamente.
Gerenciamento de ambiente e diretório de trabalho
\n\nO ProcessBuilder permite ajustar finamente a execução. Por exemplo, para executar um script com variáveis de ambiente específicas:
\n\nProcessBuilder pb = new ProcessBuilder("python3", "backup.py");\npb.directory(ne