viernes, 24 de mayo de 2013

Java - Como hacer una JVM | Parte 1

He estado trabajando en un proyecto en el que tenia que diseñar una JVM (Java Virtual Machine), y fue complicado encontrar información [1][2] sobre como hacerlo, (en español ya ni os cuento). Tengo que agradecer a un profesor llamado Jon Pearce de Saint José State University que me pasó el código fuente de parte de su especificación que me hizo saber por donde empezar [3][4].

Vista la falta de información, me he decidido a realizar este tutorial para hacer nuestra propia JVM, algo así como una Home Made Java Virtual Machine!

Conforme iba creciendo el contenido me decidí a dividir el tutorial en 3 partes, esta primera será más de teoría y preparación para tener cierta idea de que estamos haciendo, en las otras 2 partes nos meteremos con el código.

Hay muchas implementaciones de la JVM, algunas de código abierto que puedes mirar y repasar pero son muy complejas para tomar como una primera aproximación, la que expondré aquí será bastante "simple", sin soporte para varios threads ni recolector de basura y probablemente bastante ineficiente, pero que funcionará!.


La JVM

Lo primero, la JVM (Java Virtual Machine), es, como su propio nombre indica, una máquina virtual para programas Java, permite que cualquier programa Java se pueda ejecutar sobre cualquier plataforma (siempre y cuando disponga de su JVM), aquí entra el famoso 'Write once, Run anywhere' o WORA [5].

El .java se convierte en .class para la JVM
Ahora bien, para hacer eso es necesario que la JVM ejecute algo que sea común a todas las plataformas, y aquí entra el Bytecode, una especie de lenguaje ensamblador que la JVM entiende y puede ejecutar.

Los .class

¿Pero donde esta ese Bytecode? En los archivos .class, cualquiera que haya programado en Java sabrá que los archivos fuente son los .java y que al compilar se crea un archivo .class, estos .class (agrupados en bytes), son los que la JVM usa para la ejecución, conteniendo las instrucciones Bytecode además de la información de cada clase, almacenada en una estructura llamada Constant Pool [6]. Estos archivos son cargados dinámicamente en un intérprete para su ejecución (JVM).

Los .class son un mundo en sí, y si bien en este tutorial haremos referencia a ellos y habrá que entender ciertos aspectos de su estructura, será solo para su uso aplicado en la JVM, la elaboración de un parser para .class no es el objetivo de este tutorial, por suerte hay gente que se ha dedicado a hacer eso, y lo ha puesto a distribución del publico, para todo ese trabajo con .class usaremos la librería BCEL que comentaremos más adelante.

Estructura de la JVM

Podemos ver a la JVM como una maquina con tres partes, una zona de memoria llamada ClassArea, otra llamada Heap y otra para frames. La primera guarda la información de clases y métodos, en la segunda se guarda la información de objetos dinámicos cada vez que se invoca a new, además cuando un objeto deja de ser útil es eliminado por el recolector de basura, la tercera guarda la información de frames, cada frame cuenta con una pila propia, un espacio para variables locales y parámetros, e información de control (frame padre, PC y método operando).

Pila de parámetros, variables locales, información de control

La JVM sigue el patrón de un intérprete basado en pila creando un frame por cada ejecución de método, cuando el método termina el control pasa al frame padre.

La ejecución

La ejecución se hace a través de 212 instrucciones [7] que se ocupan de distintas tareas, si existen para distintos tipos usan un prefijo (i para integer, d para double, etc).
  • Operaciones con la pila: poner elementos (bipush), quitarlos (pop), duplicarlos (dup), etc.
  • Aritméticas: sumas, restas, multiplicaciones de distintos tipos (iadd, fadd, dmul).
  • Carga y guardado: cargar (iload) o guardar elementos desde memoria local (istore).
  • Control de flujo: salto mediante comparación de valores (if_icmpeq), a subrutina, por excepciones (athrow).
  • Acceso a campos: para acceder a campos de clases (getfield, putfield, etc).
  • Invocación de métodos: para invocar métodos ya sean estáticos (invokestatic), de interfaces (invokeinterface), etc.
  • Objetos: para reservar memoria para objetos (new) o arrays (newarray).
  • Conversión de tipos: cambio de un tipo (f2i) a otro o comprobación de tipos (checkcast).

BCEL

La Librería BCEL (Byte Code Engineering Library) [8] es una librería para analizar, manipular y crear archivos Java Class. En nuestro contexto es una ayuda para parsear los archivos .class y obtener toda la información que necesitamos para la ejecución del bytecode en nuestra máquina virtual de Java.

Necesitaremos hacer uso de dos de los paquetes principales de BCEL.

El primero se encarga de la parte estática teniendo su clase principal en JavaClass (org.apache.bcel.classfile). Con ella podemos acceder a la información guardada en .class sin preocuparnos de lecturas de bajo nivel, aunque no es posible ningún tipo de modificación. Podemos ver un diagrama de su estructura en la siguiente imagen.

Estructura JavaClass
Con la segunda ClassGen (org.apache.bcel.generic) podemos acceder a alguna de las opciones de la primera además de poder llevar a cabo modificaciones.

Estructura ClassGen
Ambas puedes ser útiles según el contexto, aunque no es necesario hacer modificaciones en el bytecode pues solo vamos a ejecutarlo, ClassGen da acceso a algunos elementos que son necesarios a la hora de realizar la ejecución del mismo de una manera más simple, como la lista de instrucciones encapsulada en InstructionHandle. A pesar de eso hablaremos de otra forma de ejecución que lee instrucciones directamente del bytecode a través del method attribute code.

Bueno hasta aquí esta introducción teórica, bastante densa y con mucho tema pero que me parece imprescindible para poder meternos en la siguiente parte con el código.

Un saludo!

Segunda Parte ya disponible!
Tercera Parte ya disponible!

Algunas referencias que queda bonico
[1] Especificación oficial, mu bonica pero un tochaco.
[2] El punto de referencia que usé para desarrollar la JVM, ésta hecha en en C++ y con parser incluido.
[3] Información bastante básica, empecé por aquí.
[4] Esqueleto básico de una JVM.
[5] El lema de Java.
[6] La Constant Pool y el mundo de las referencias indirectas (ya entenderéis xd).
[7] Lista de instrucciones de la JVM
[8] Explicaciones sobre el uso de BCEL y la JVM, algo como esto pero en inglés .

No hay comentarios:

Publicar un comentario

Ponte un nombre aunque sea falso, que Anó-nimo queda mu feo :(