Java para programación de sistemas: cuando un lenguaje "pesado" se convierte en una herramienta ligera
\n\nCuando escuchas "programación de sistemas", normalmente te vienen a la mente asociaciones con C, ensamblador, punteros desnudos y gestión manual de memoria. Java es lo último que se te ocurre. Y es una lástima. En más de veinte años de evolución, Java ha pasado de ser un "monstruo lento para sitios corporativos" a una herramienta completa para escribir utilidades de sistema, demonios de red, monitoreo de recursos e incluso componentes de sistemas operativos.
\n\nPor supuesto, Java no reemplazará a C al escribir un controlador de tarjeta gráfica. Pero para el 90% de las tareas de programación de sistemas —trabajo con procesos, descriptores de archivos, sockets de red, gestión de memoria a nivel de SO— Java ofrece un enfoque elegante, seguro y multiplataforma. En este artículo, analizaremos ejemplos reales de cómo usar Java para programación de sistemas y mostraremos que el código en Java puede ser tan eficiente como en C++, pero mucho más legible.
\n\n¿Por qué Java no es solo "enterprise"?
\n\nEl estereotipo principal: Java se ralentiza debido a la recolección de basura y la máquina virtual. Esto era cierto en 2004. Hoy en día, la JVM utiliza compilación a código máquina (JIT), predicción de bifurcaciones y recolección de basura generacional con pausas de menos de un milisegundo. Para la programación de sistemas, tres cosas son críticas:
\n\n- \n
- Multiplataforma — una utilidad funciona en Linux, Windows y macOS sin recompilación. \n
- Seguridad de memoria — sin segfault, sin fugas de memoria (en el sentido clásico). \n
- Biblioteca estándar rica — trabajo con procesos, NIO, sockets, canales de archivos, todo integrado. \n
Son precisamente estas cualidades las que hacen de Java una excelente opción para escribir utilidades de sistema que deben funcionar de manera estable y predecible.
\n\nTrabajo con procesos: ProcessBuilder y Runtime.exec
\n\nCualquier utilidad de sistema, tarde o temprano, ejecuta programas externos. En Java, esto se maneja con Runtime.exec() y el más flexible ProcessBuilder. El segundo es preferible: permite controlar el entorno, el directorio de trabajo y redirigir los flujos de entrada/salida.
Ejemplo: monitoreo del uso de memoria (Linux)
\n\nSupongamos que queremos obtener el porcentaje de RAM utilizada. En lugar de analizar /proc/meminfo manualmente, podemos ejecutar free y procesar su salida:
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("Memoria (MB):");\n System.out.println(output.toString());\n } else {\n System.err.println("Error al llamar a free");\n }\n }\n}\n\nEste código es un ejemplo típico de programación de sistemas en Java: ejecutar un proceso nativo, leer su salida, manejar errores. Importante: use siempre redirectErrorStream(true) para evitar un deadlock al leer dos flujos (stdout y stderr) simultáneamente.
Gestión del entorno y directorio de trabajo
\n\nProcessBuilder permite ajustar finamente la ejecución. Por ejemplo, para ejecutar un script con variables de entorno específicas:
\n\nProcessBuilder pb = new ProcessBuilder("python3", "backup.py");\npb.directory(ne