El bytecode de Java es el conjunto de instrucciones de la máquina virtual Java (JVM), el lenguaje en el que se compila Java y otros códigos fuente compatibles con JVM . [1] Cada instrucción está representada por un solo byte , de ahí el nombre bytecode , lo que lo convierte en una forma compacta de datos . [2]
Debido a la naturaleza del bytecode, un programa de bytecode Java se puede ejecutar en cualquier máquina con una JVM compatible; sin el largo proceso de compilación desde el código fuente.
El código de bytes de Java se utiliza en tiempo de ejecución, ya sea interpretado por una JVM o compilado en código de máquina mediante compilación just-in-time (JIT) y se ejecuta como una aplicación nativa.
Como el código de bytes de Java está diseñado para una compatibilidad y seguridad entre plataformas, una aplicación de código de bytes de Java tiende a ejecutarse de manera consistente en varias configuraciones de hardware y software . [3]
En general, un programador Java no necesita comprender el bytecode de Java ni siquiera conocerlo. Sin embargo, como se sugiere en la revista IBM developerWorks, "comprender el bytecode y qué bytecode es probable que genere un compilador Java ayuda al programador Java de la misma manera que el conocimiento del lenguaje ensamblador ayuda al programador C o C++ ". [4]
El código de bytes comprende varios tipos de instrucciones, incluida la manipulación de datos, la transferencia de control, la creación y manipulación de objetos y la invocación de métodos, todos ellos parte integral del modelo de programación orientada a objetos de Java. [1]
La JVM es a la vez una máquina de pila y una máquina de registros . Cada marco para una llamada de método tiene una "pila de operandos" y una matriz de "variables locales". [5] : 2.6 [2] La pila de operandos se utiliza para los operandos de los cálculos y para recibir el valor de retorno de un método llamado, mientras que las variables locales sirven para el mismo propósito que los registros y también se utilizan para pasar argumentos de método. El tamaño máximo de la pila de operandos y la matriz de variables locales, calculado por el compilador, es parte de los atributos de cada método. [5] : 4.7.3 Cada uno puede tener un tamaño independiente de 0 a 65535 valores, donde cada valor es de 32 bits. long
y double
los tipos, que son de 64 bits, ocupan dos variables locales consecutivas [5] : 2.6.1 (que no necesitan estar alineadas a 64 bits en la matriz de variables locales) o un valor en la pila de operandos (pero se cuentan como dos unidades en la profundidad de la pila). [5] : 2.6.2
Cada código de bytes se compone de un byte que representa el código de operación , junto con cero o más bytes para los operandos. [5] : 2.11
De los 256 códigos de operación de bytes de longitud posibles , a partir de 2015 [actualizar], 202 están en uso (~79%), 51 están reservados para uso futuro (~20%) y 3 instrucciones (~1%) están reservadas permanentemente para que las usen las implementaciones de JVM. [5] : 6.2 Dos de estos ( impdep1
y impdep2
) son para proporcionar trampas para software y hardware específicos de la implementación, respectivamente. El tercero se utiliza para que los depuradores implementen puntos de interrupción.
Las instrucciones se dividen en varios grupos amplios:
aload_0
, istore
)ladd
, fcmpl
)i2b
, d2i
)new
, putfield
)swap
, dup2
)ifeq
, goto
)invokespecial
, areturn
)También hay algunas instrucciones para una serie de tareas más especializadas, como lanzamiento de excepciones, sincronización, etc.
Muchas instrucciones tienen prefijos y/o sufijos que hacen referencia a los tipos de operandos sobre los que operan. [5] : 2.11.1 Estos son los siguientes:
Prefijo/sufijo | Tipo de operando |
---|---|
i | entero |
l | largo |
s | corto |
b | byte |
c | personaje |
f | flotar |
d | doble |
a | referencia |
Por ejemplo, iadd
sumará dos números enteros, mientras que dadd
sumará dos números dobles. Las instrucciones const
, load
, y store
también pueden tomar un sufijo de la forma , donde n es un número del 0 al 3 para y . El máximo n para difiere según el tipo._n
load
store
const
Las const
instrucciones insertan un valor del tipo especificado en la pila. Por ejemplo, iconst_5
insertará un entero (valor de 32 bits) con el valor 5 en la pila, mientras que dconst_1
insertará un doble (valor de punto flotante de 64 bits) con el valor 1 en la pila. También hay un aconst_null
, que inserta una null
referencia. La n para las instrucciones load
y store
especifica el índice en la matriz de variables locales desde donde cargar o donde almacenar. La aload_0
instrucción inserta el objeto en la variable local 0 en la pila (normalmente es el this
objeto). istore_1
almacena el entero en la parte superior de la pila en la variable local 1. Para las variables locales más allá de 3, se omite el sufijo y se deben utilizar operandos.
Considere el siguiente código Java:
exterior : para ( int i = 2 ; i < 1000 ; i ++ ) { para ( int j = 2 ; j < i ; j ++ ) { si ( i % j == 0 ) continuar exterior ; } System . out . println ( i ); }
Un compilador de Java podría traducir el código Java anterior en código de bytes de la siguiente manera, suponiendo que lo anterior se colocó en un método:
0 : iconst_2 1 : istore_1 2 : iload_1 3 : sipush 1000 6 : if_icmpge 44 9 : iconst_2 10 : istore_2 11 : iload_2 12 : iload_1 13 : if_icmpge 31 16 : iload_1 17 : iload_2 18 : irem 19 : ifne 25 22 : goto 38 25 : iinc 2, 1 28 : goto 11 31 : getstatic #84; // Campo java/lang/System.out : Ljava/io/PrintStream; 34 : iload_1 35 : invocarvirtual #85 ; // Método java/io/PrintStream.println:(I)V 38 : iinc 1, 1 41 : ir a 2 44 : regresar
El lenguaje más común que se dirige a la máquina virtual Java mediante la producción de bytecode de Java es Java. Originalmente, solo existía un compilador, el compilador javac de Sun Microsystems , que compila el código fuente de Java en bytecode de Java; pero como ahora están disponibles todas las especificaciones para el bytecode de Java, otras partes han proporcionado compiladores que producen bytecode de Java. Algunos ejemplos de otros compiladores incluyen:
Algunos proyectos proporcionan ensambladores de Java para permitir escribir código de bytes de Java a mano. El código de ensamblaje también puede generarse por máquina, por ejemplo, mediante un compilador que tenga como destino una máquina virtual de Java . Entre los ensambladores de Java más destacados se incluyen:
Otros han desarrollado compiladores, para diferentes lenguajes de programación, para apuntar a la máquina virtual Java, como:
En la actualidad, existen varias máquinas virtuales Java disponibles para ejecutar bytecode de Java, tanto productos gratuitos como comerciales. Si no se desea ejecutar bytecode en una máquina virtual, un desarrollador también puede compilar código fuente de Java o bytecode directamente en código de máquina nativo con herramientas como el compilador GNU para Java (GCJ). Algunos procesadores pueden ejecutar bytecode de Java de forma nativa. Dichos procesadores se denominan procesadores Java .
La máquina virtual Java ofrece cierto soporte para lenguajes de tipado dinámico . La mayor parte del conjunto de instrucciones de la JVM existente tiene tipado estático , en el sentido de que las firmas de las llamadas a métodos se comprueban en tiempo de compilación , sin un mecanismo para posponer esta decisión hasta el tiempo de ejecución o para elegir el envío del método mediante un enfoque alternativo. [12]
JSR 292 ( Compatibilidad con lenguajes de tipado dinámico en la plataforma Java ) [13] agregó una nueva invokedynamic
instrucción en el nivel de JVM para permitir la invocación de métodos que dependen de la verificación de tipos dinámica (en lugar de la instrucción de verificación de tipos estática existente invokevirtual
). La máquina Da Vinci es una implementación de máquina virtual prototipo que aloja extensiones de JVM destinadas a admitir lenguajes dinámicos. Todas las JVM que admiten JSE 7 también incluyen el invokedynamic
código de operación.