• Últimas noticias

    5. La máquina virtual de Java

    Quinto episodio del curso de Java.

    Java es un lenguaje múltiplataforma y además se caracteriza por que sus programas son rápidos, pero ¿cómo hizo James Gosling para lograr esto?


    índice de este curso


    Versión de este curso en video


    Si querés podés ver el video correspondiente a este curso


    Introducción

    En los episodios anteriores vimos conceptos de compiladores e interpretes, y en la última entrega de este curso, en donde hablamos de la historia de Java, hicimos mucho énfasis en que una de las características más importantes de java es el hecho de ser multiplataforma.

    Esto nos puede hace pensar que java funciona con un intérprete.

    También dijimos que Java es utilizado en aplicaciones webs, aplicaciones móviles, aplicaciones de escritorio, aplicaciones en servidores, videojuegos, y muchos otros ámbitos, por lo que un interprete que sacrifique la velocidad de ejecución a cambio de ser multiplataforma no sería conveniente, y  Java no es un lenguaje con el que se hagan aplicaciones particularmente lentas.

    Por tanto si Java es multiplataforma y además sus aplicaciones son rápidas nos surge la duda si Java utiliza un intérprete o un compilador.
    Se podría pensar en que tiene ambos y no se estaría muy equivocado si eso es lo que piensan. Pero ¿cómo es que funcionan ambos elementos en la traducción de un programa?

    La máquina virtual de Java (JVM)

    Básicamente Java compila su código a lo que se conoce como código intermedio, que tiene el nombre de ByteCode, y que es como un código ensamblador pero inventado por Java. Este código no es leído directamente por el microprocesador, ya que al no estar en código de máquina, este no lo entendería, por lo que primero pasa por algo que se conoce como máquina virtual de Java.

    Una máquina virtual es un software que simula una computadora por decirlo de alguna forma, y puede ejecutar programas como si fuera una computadora real.
    Esta computadora virtual no necesariamente tienen que tener equivalencia con el hardware real sobre el cual se la esta ejecutando.

    La máquina virtual de Java traduce el ByteCode inicialmente compilado por Java a código de máquina sobre la plataforma en la cual se está ejecutando el programa. Esto quiere decir que la gente de Java tuvo que haber creado una máquina virtual para cada plataforma existente, y efectivamente eso es lo que hicieron. En casi todos los dispositivos se puede encontrar una versión de la máquina virtual de Java, y lo mejor de todo es que en muchos casos ésta viene instalada de fabrica, como por ejemplo en Android.



    Compilador Just In Time (JIT)

    Y como es que realiza la traducción la máquina virtual de Java? En principio podríamos decir que la traducción se hace mediante un intérprete, pero en realidad se hace con un concepto similar que se llama compilador Just in Time, y acá es donde esta el truco para que un programa de Java sea rápido.

    El compilador Just in Time compila ByteCode a código de máquina en tiempo de ejecución, por lo que se preguntarán, ¿ésta no es la definición de intérprete? Si pero hay unas diferencias con el intérprete.

    Primero, el compilador Just in Time hace un recuento de los métodos que se usan frecuentemente, y los compila al iniciar la máquina virtual de Java.

    Luego los métodos que no son utilizados frecuentemente los compilará mucho más tarde e incluso nunca.

    Esto es así para que el inicio de la máquina virtual de Java sea rápido y luego siga teniendo un buen rendimiento.


    ¿Y por qué no compilar todo el ByteCode de una vez en lugar de utilizar el compilador Just In Time?

    Por un lado no se sabe que tan grande es el programa, por lo tanto, no sería agradable tener que esperar 2, 5, 10, o quien sabe cuantos minutos para solo iniciar el programa.

    Por otro lado el compilador Just in Time implementa algunos trucos para hacer que la ejecución del programa termine siendo más rápida, por ejemplo, vamos a ver este código
     public test(int nro) {
        if(nro == 20) {
           // muchas líneas de código que hacen cosas
        } else if (nro >= 5) {
           // muchas otras líneas de código que hacen otras cosas
        } else {
            return;
        }
    
    

    El compilador puede llamar 1 o 2 veces a este método y darse cuenta sobre la marcha que si el número que se ingresa en la función es menor a 5 entonces la función termina sin hacer nada, y terminar sin hacer nada significa que tuvo que perder tiempo preguntando si el número ingresado por parámetro es igual a 20, si es mayor o igual a 5, e incluso pueden haber muchas otras condiciones más anidadas.

    Entonces lo que hace el compilador Just in Time es reemplazar la llamada al método test(nro) por un código como
     if(nro>4){
       public test(int nro) {
        if(nro == 20) {
           // muchas líneas de código que hacen cosas
        } else if (nro >= 5) {
           // muchas otras líneas de código que hacen otras cosas
        } else {
            return;
        }
     }
    
    

    lo que elimina la llamada al método por completo si el número que se pasa es menor a 5.
    Así como sucede con este método, el compilador Just in Time  va analizando el código y hace este tipo de trucos en muchas otras partes del código.

    Si se quisiera pre-compilar el código, o sea, compilar todo de una sola vez antes de iniciar el programa, esto no sucedería, y todos esos if anidados se ejecutarían. Obviamente como programador podría encargarme de hacer este tipo de comprobaciones y optimizar el código, pero escribir este tipo de código y tener que reconocer estos patrones en todo el proyecto puede hacer que escribir el programa se convierta en una pesadilla.

    Proceso completo que realiza la JVM

    Pasando en limpio el proceso que se realiza para que el código que se escriba en Java sea entendido finalmente por la computadora podemos decir que:
    • En primer lugar se compila el código a ByteCode, el cual es volcado en archivos de extension .class 
    • El ByteCode es un conjunto de instrucciones que entiende la máquina virtual de java. 
    • Mediante un compilador Just in Time la máquina virtual de Java traduce el ByteCode a código de máquina en tiempo de ejecución. 
    • Y así la máquina entiende y puede ejecutar el programa.

    JAR

    Como se mencionó anteriormente, cuando se compila el código de Java se genera uno o más archivos .class que contienen el código traducido a ByteCode de mi programa. Esto hace que pueda tener muchos archivos por separado de lo que es mi programa, y que para ejecutarlos tenga que correr algún comando en la consola de comandos, o tener algun IDE de programación en Java. Para facilitar la distribución de aplicaciones los archivos de clase pueden empaquetarse juntos en un archivo con formato Jar.

    Tener un Jar es como de alguna manera tener un Exe, al hacer doble clic ejecutará mi programa.

    Compilador Ahead Of Time (AOT)

    Quiero mencionar también la existencia de un compilador especial que se llama Ahead of Time, y que es de alguna forma una variante del Just in Time. Este compilador no se ejecuta junto con el Just in Time sino que se usaría en reemplazo del mismo y por defecto siempre estaremos usando el Just in Time ya que el compilador AOT está inhabilitado de forma predeterminada.

    El compilador Ahead of Time que en español podemos traducir como antes de tiempo, compila en programa antes de hacer la ejecución del mismo y genera un fichero ejecutable de forma nativa por la plataforma donde se quiere ejecutar el programa. O sea que este concepto rompe con la pregunta que  se había planteado anteriormente:  ¿por qué no compilar todo antes de ejecutar el programa?.

    Este compilador combina ciertas variantes para optimizar el código generado, como por ejemplo el uso en primer lugar de un intérprete de un compilador Just in Time a fin de analizar las partes de código que se usan y de que manera, para luego generar un programa nativo optimizado para los escenarios observados en ejecuciones anteriores del mismo programa.

    Una desventaja del compilador AOT es que genera un archivo ejecutable para la plataforma sobre la cual se está trabajando por lo que rompe con la idea de que un programa en Java es multiplataforma. Pero en casos muy específicos donde se necesite un mejor rendimiento del programa y se sepa de antemano para que plataforma se va a ejecutar puede ser una buena opción.

    De todas maneras según IBM, el código generado por el compilador AOT no se ejecuta tan bien como el generado por JIT aunque la definición teórica nos indique otra cosa.

    JDK y JRE

    Junto con el concepto de la máquina virtual de Java entran los términos JDK y JRE.

    El JDK o Java Development Kit que español se traduce como kit de desarrollo de Java, contiene las herramientas y librerías necesarias para crear y ejecutar aplicaciones de Java. Si no se tiene instalado ningún JDK no se podrá hacer ninguna aplicación.

    Algunas utilidades con las que cuenta el JDK son:

    • Javac. basicamente es el compilador de Java y es el que se encarga de traducir el código de fuente que escribimos en Java a ByteCode.
    • Java. Es el lanzador de aplicaciones en Java y ejecuta el bytecode a partir de los archivos .class
    • AppletViewer. Es una herramienta que permite visualizar applets sin la necesidad de hacerlo en un navegador Web, entendiendo que un applet es una aplicación de java que se ejecuta en un navegador Web.
    • Javadoc. Herramienta de Java que permite generar documentación automatica en formato HTML a partir de comentarios especiales que se ponen en el código de la aplicación.
    • Jar. Es una herramienta que sirve para crear y gestionar los archivos de empaquetado JAR.
    • JDB. Es el Java Debugger que sirve para depurar programas.
    • Javap. Es una herramienta para desensamblar archivos .class.

    Hay otras utilidades más, pero estas son las más importantes y fácil de entender en este momento.

    Por otro lado tenemos el JRE o Java Runtime Enviroment que traducido al español es Entorno de ejecución de Java, que es un conjunto de utilidades que permite la ejecución de programas en Java y dicho de otra manera es la máquina virtual de Java (junto con las librerías o API de JAVA). Al igual que se mencionó con el JDK, si no tenemos instalado el JRE no podremos ejecutar ninguna aplicación hecha en Java.

    Cabe aclarar que si somos desarrolladores y nos disponemos a instalar el JDK no hará falta instalar el JRE ya que este viene incluido en el anterior.


    Conclusión

    La máquina virtual de Java por tanto, es lo que hace que los programas en Java sean multiplataforma, rápidos y de alto rendimiento, y que esto formara parte de que Java sea un lenguaje masivamente utilizado.

    En la próxima entrega veremos las características que involucran a Java, las cuales en su conjunto hicieron de éste un lenguaje tan aceptado y usado por toda una comunidad inmensa de programadores.


    ANTERIOR
    4. La historia completa de JAVA
    SIGUIENTE
    6. Características de JAVA

    Autor: Pablo Jasinski

    Profesor en Escuela Técnica UBA y Escuela Técnica Nº35, especializado en materias de programación, arquitectura de las computadoras y redes. Profesional en el área de programación y desarrollo de videojuegos.

    No hay comentarios