Lo importante de Java es su máquina virtual
En los últimos 10 años Java se convirtió en uno de los lenguajes de programación mas empleados. Pero a pesar de su popularidad todavía existen muchos programadores que no han descubierto que es lo que hace que Java sea tan especial.
En realidad Java es más que un lenguaje de programación. Tomando en cuenta nada más lo que podríamos llamar el “núcleo” de Java (es decir sin fijarnos en Java Enterprise Edition, Java Micro Edition, etc.) se puede decir que Java esta formado por:
- El lenguaje Java
- Las bibliotecas estándar (libraries)
- La máquina virtual (JVM)
El lenguaje Java es un lenguaje bien diseñado, sencillo y relativamente fácil de aprender, pero por si solo no tiene nada en especial que lo haga realmente superior a otros lenguajes que ya existían anteriormente. Sus bibliotecas estándar son muy completas y reducen bastante el esfuerzo necesario para escribir aplicaciones, y se podría decir que la existencia de estas bibliotecas estándar son en cierta manera más importantes que el lenguaje en sí. La parte realmente revolucionaria de Java es su máquina virtual, y para entender por que es tan importante primero hay que entender cuales son los problemas que intenta resolver.
Los orígenes de Java
A principios de los 90’s un grupo de ingenieros dentro de Sun Microsystems estaba intentando crear un nuevo modelo de software para productos de electrónica de consumo tales como teléfonos, televisiones y videocaseteras. En la actualidad cada uno de estos productos contiene uno, ó más, microprocesadores y mucha de su funcionalidad depende de ellos. El software para estos procesadores está en ROM y por lo tanto la única manera de cambiarlo es quitar el ROM y colocar uno nuevo. La idea de estos ingenieros era darle un valor agregado a esos productos por medio de software más dinámico el cuál pudiera ser modificado a distancia por medio de una red, la línea telefónica ó una señal de televisión. De esta manera los diferentes proveedores de servicios podrían ofrecer nuevas opciones basadas en dispositivos más inteligentes.
Inicialmente la idea era emplear C++ como lenguaje de desarrollo pero pronto se dieron cuenta que la electrónica de consumo tiene unos requerimientos estrictos que no era posible satisfacer con este lenguaje. Efectivamente, los productos de electrónica de consumo a pesar de su aparente sencillez tienen requerimientos más estrictos que los que se acostumbran en la computación tradicional:
- Rápido. El tiempo de respuesta se mide en décimas ó centésimas de segundo. Cuando subo ó bajo el volumen en una televisión ó al cambiar de un canal a otro espero una respuesta instantánea. Aquí un segundo puede parecer una eternidad.
- Multithreaded. Todos estos dispositivos realizan varias tareas simultáneamente. En una videocasetera el microprocesador tiene que al mismo tiempo estar actualizando el display del reloj y monitoreando si llegan señales del control remoto. Si llega alguna orden desde el control remoto hay que ejecutarla y al mismo tiempo seguir monitoreando por si llegan más órdenes.
- 100% Confiable. El dispositivo nunca debe fallar por un bug en el software. Una televisión que no responde cuando trato de cambiar de canal ó cambia sin que nadie se lo ordene es una televisión inservible.
- Compacto. Los fabricantes de productos para electrónica de consumo son muy sensibles al precio de cada uno de los componentes y esperan gastar $10 USD ó menos en la electrónica digital de su producto.
- Diferentes Arquitecturas. Una vez más el costo es el factor más importante. Se emplea el procesador de menor costo que cumpla con la funcionalidad deseada aunque no sea compatible con los procesadores empleados en modelos anteriores. La base instalada es totalmente heterogénea y cada año aparecen productos que emplean procesadores con nuevas arquitecturas.
- Compatibilidad. Al comprar un teléfono espero poder hablar con cualquier otra persona que también tenga un teléfono sin importar cuál sea su marca. Y basta con una sola televisión para poder ver cualquier programa sin importar que tan viejo o tan nuevo sea ¿cuando han oído de alguien que no pudo ver El Mundial de Futbol 2006 porque tenía Televisión 2004?
- Invisible. Imagínense una televisión que hay que prender varios minutos antes de que empiece un programa para darle tiempo de “bootear” su software. ¡Nadie quiere una televisión así! El software debe hacer su trabajo sin que el usuario tenga que enterarse de su existencia.
Al tratar de hacer software que cumpliera con estos requerimientos y también pudiera modificarse dinámicamente, los ingenieros de Sun se vieron obligados a idear una nueva tecnología para lenguajes de programación. Java es una implementación de esa nueva tecnología.
Un nuevo modelo de compilación/ejecución
El punto importante de Java es la posibilidad de escribir software más dinámico y al mismo tiempo seguro y confiable. Software que, sin dejar de ser compilado, pueda cambiar mientras se está ejecutando.
Para poder tener software tan dinámico se presentan dos problemas. El primero es el modelo actual para lenguajes compilados en el cual la generación de un ejecutable y posteriormente su ejecución forman dos fases totalmente separadas. El otro problema es el cómo poder garantizar que un programa siga funcionando correctamente mientras que se le pegan piezas que pueden provenir de cualquier lugar.
Como mencionamos anteriormente la idea original era emplear C++ para crear este tipo de software, pero las limitaciones del lenguaje los convencieron de que era necesario crear un nuevo lenguaje basado en una tecnología más moderna. Veamos en que consiste esta nueva tecnología y en qué difiere de la anterior.
La principal diferencia entre Java y C++ está en el modelo de compilación/ejecución empleado. Tal como lo muestra la figura, en C++ el compilador traduce cada archivo fuente en un módulo objeto y después un ligador (linker) los liga con otros módulos objeto extraídos de una biblioteca (library) para generar un ejecutable. Este modelo de compilación es exactamente el mismo que emplea el lenguaje C, el cual a su vez es idéntico al empleado en Fortran desde hace casi 40 años. El único cambio que este modelo a tenido en cuatro décadas ha sido la introducción de bibliotecas dinámicas las cuales, además de ser diferentes de un sistema operativo a otro, pueden tener restricciones severas sobre cómo deben estar programadas.
La siguiente figura muestra el modelo de compilación/ejecución en Java. Por ser Java un lenguaje 100% orientado a objetos, en cualquier programa todos los estatutos ejecutables forman parte de la definición de alguna clase, y cada archivo fuente puede contener la definición de una ó más clases. El compilador genera un archivo de código por cada una de las clases; el nombre del archivo está formado por el nombre de la clase seguido de la extensión “.class”. Estos archivos de código están listos para ser ejecutados apenas termina la compilación, sin que sea necesario pasarlos por un ligador. Ni siquiera es necesario juntarlos todos en un sólo archivo, a la hora de ejecución se van cargando cada uno de ellos según se vayan necesitando. 
Al código generado por el compilador se le llama “Java bytecodes” que es el lenguaje de una máquina virtual de Java. Estos bytecodes fueron diseñados para cumplir varios requisitos:
- Compactos. Un programa traducido a bytecodes es de 2 a 3 veces más compacto que el mismo programa traducido a lenguaje de máquina de un procesador tradicional.
- Fáciles de interpretar. Un intérprete de bytecodes es sencillo y puede ser muy eficiente. Hay quienes creen que por ejecutarse interpretando bytecodes un programa en Java va a ser lento. La realidad es que aunque Java interpretado no es tan rápido cómo C ó C++, tampoco se le puede llamar lento.
- Fáciles de traducir. Traducir de Java bytecodes a lenguaje de máquina optimizado para un procesador Intel ó RISC es relativamente sencillo. Algunas implementaciones de la JVM en vez de interpretar los bytecodes a la hora de ejecución, los traducen a lenguaje de máquina del procesador en el que se está ejecutando. A esto se le llama “Just In Time compilation” (JIT). Empleando tecnología JIT los programas en Java son tan rápidos como programas en C++, y en ciertos casos con la tecnología Hotspot hasta llegan a ser más rápidos en ciertos casos.
- Fáciles de implementar en hardware. La JVM tiene una arquitectura sencilla, lo cuál simplifica su implementación en hardware. Un procesador de Java puede ser barato.
- Verificables. Java está diseñado para que sea imposible accesar ilegalmente algún área de memoria. Es posible verificar una secuencia de bytecodes antes de ejecutarlos para validar que cumplan con las reglas de acceso a memoria de Java.
Un modelo de seguridad
Una de las principales características de Java es la facilidad con la cuál se puede anexar nuevo código a un programa mientras que este se está ejecutando. Es indispensable poder garantizar que el programa siga funcionando correctamente aunque el nuevo código tenga errores. Y si tomamos en cuenta que este nuevo código puede provenir de cualquier lugar en la red hay que considerar la posibilidad de que sea un virus ó un espía.
La solución de Java a este problema consiste en tres mecanismos de seguridad:
- El lenguaje. En Java es imposible que una rutina accese una dirección de memoria a la cuál no debe tener acceso. Por otra parte, su sistema para manejo de excepciones permite escribir programas que se recuperen automáticamente de cualquier error al ejecutar código cargado dinámicamente.
- El verificador. Aunque Java no permite escribir programas que accesen de manera inválida alguna dirección de memoria, siempre existe la posibilidad de que alguien intente escribir un programa empleando directamente los bytecodes para sacarle la vuelta al compilador. Al cargar nuevo código el verificador puede checar que los bytecodes cumplan con las reglas del lenguaje.
- El administrador de seguridad. La JVM recuerda de donde proviene cada pieza de código y puede hacer que se ejecute dentro de un ambiente protegido en donde se controlan estrictamente los accesos a la red y al disco duro.
Varios aspectos del lenguaje Java colaboran para poder garantizar que no haya forma de poder accesar de manera inválida alguna dirección de memoria:
- No hay apuntadores ni aritmética de apuntadores. Los apuntadores fueron reemplazados por referencias. No existe manera de asignar un valor numérico arbitrario a una referencia porque esto permitiría tener acceso a cualquier dirección de memoria.
- Siempre se valida el rango de los subíndices en los accesos a arreglos. Si no fuera así se podrían emplear subíndices negativos ó mayores al tamaño del arreglo para poder tener acceso a otras áreas de memoria.
- Maneja recuperación automática de memoria (garbage collection). La JVM automáticamente recupera la memoria que ya no puede ser accesada desde ninguna parte del programa. Sí el programador pudiera explícitamente devolverle memoria al sistema existiría el peligro de que conserve una referencia al área que devolvió y de esta manera pudiera seguir teniendo acceso a ella.
- Nuevas áreas de memoria siempre se inicializan en ceros. De no ser así, alguien podría crear un objeto que contenga referencias y usar la basura dentro de sus referencias no inicializadas para poder accesar otras áreas de memoria.
- A la hora de ejecución la JVM determina la representación en memoria de cada tipo de objeto. En C++ el compilador determina la representación en memoria de cada tipo de objeto, en particular el número de bytes que ocupa y a cuantos bytes a partir del principio del objeto se encuentra cada una de sus variables. Si Java hiciera esto, alguien podría engañar al compilador para que genere código que haga accesos ilegales a memoria.
Todos estos aspectos de Java también ayudan a evitar muchos errores de programación pero no hay que olvidar que esa no es su razón de ser. Es común oír comentarios cómo “Lo que me gusta de Java es que valida el rango de los subíndices en los accesos a arreglos y puedo rápidamente detectar ese tipo de errores en mis programas”, ó al revés “No me gusta que Java pierda tiempo validando el rango de los subíndices. Yo sé lo que estoy haciendo”. La realidad es que no se trata de me gusta ó no me gusta, es así porque así tiene que ser para poder garantizar que nuevo código que se anexa a un programa no pueda interferir con su buen funcionamiento.
Conclusión
Java es más que un lenguaje de programación y su maquina virtual es un concepto revolucionario. En el futuro próximo podemos esperar que muchos nuevos lenguajes sigan este modelo. Por ahora ya está el caso de Microsoft con su CLR (Common Language Runtime) empleado para su lenguaje C# y toda la infraestructura de .NET, y también PARROT que se espera sea la máquina virtual para las futuras versiones de Perl.

hace 1 año 11 semanas
hace 1 año 19 semanas
hace 1 año 19 semanas
hace 1 año 19 semanas
hace 1 año 19 semanas
hace 1 año 19 semanas
hace 1 año 19 semanas
hace 1 año 19 semanas
hace 1 año 19 semanas
hace 1 año 19 semanas