Una instrucción de máquina virtual Java consta de un código de operación de longitud de bytes (código de operación) que representa un significado específico, seguido de cero o más operandos que representan los parámetros de operación. Muchas instrucciones en la máquina virtual no contienen operandos, solo un código de operación. Si se ignora la excepción, el intérprete JVM puede funcionar eficazmente con un solo código.
Copie el código de código de la siguiente manera:
hacer{
Calcule automáticamente el registro de la PC y recupere el código de operación de la ubicación del registro de la PC
si (el operando existe) saque el operando;
Realice la operación definida por el código de operación;
} while (procesar el siguiente bucle)
El número y la longitud de los operandos dependen del código de operación. Si la longitud de un operando excede un byte, se almacenará en orden Big-Endian (primer código de bytes de gama alta) y su valor debe ser (byte1<<8) | byte2.
El flujo de instrucciones de código de bytes está alineado de un solo byte, con la excepción de las instrucciones "tableswitch" y "lookupswitch". Sus operandos son especiales y deben reservarse espacios correspondientes para lograr la alineación.
Limitar la longitud del código de operación de la máquina virtual Java a un byte y renunciar a la alineación de la longitud de los parámetros del código compilado es obtener un código compilado corto y sencillo, aunque puede costarle a la implementación de JVM un cierto costo de rendimiento. Dado que el código de operación solo puede tener un byte de longitud, limita el número de conjuntos de instrucciones y no supone que los datos estén bien alineados, lo que significa que cuando los datos exceden un byte, la estructura de datos específica debe reconstruirse a partir de los bytes. Se perderá algo de rendimiento.
Tipos de datos y máquina virtual Java
En el conjunto de instrucciones de la JVM, la mayoría de las instrucciones contienen información del tipo de datos correspondiente a sus operaciones. Por ejemplo, la instrucción iload carga datos de tipo int de la tabla de variables locales en la pila de operandos, mientras que fload carga datos de tipo float.
Para la mayoría de las instrucciones de código de bytes relacionadas con tipos de datos, sus mnemónicos de código de operación tienen caracteres especiales para indicar: i representa el tipo int, l representa largo, s representa corto, b representa byte, c representa char, f representa flotante, d representa doble y a significa referencia. Hay directivas independientes que se pueden utilizar para convertir tipos no admitidos en tipos admitidos cuando sea necesario.
instrucciones de carga y almacenamiento
Las instrucciones de carga y almacenamiento se utilizan para transferir datos hacia y desde la tabla de variables locales del marco de la pila y la pila de operandos.
1) Las instrucciones para cargar una variable local en la pila de operandos incluyen: iload, iload_<n>, lload, lload_<n>, float, fload_<n>, dload, dload_<n>, aload, aload_<n>.
2) Instrucciones para almacenar un valor de la pila de operandos en la variable local: istore,istore_<n>,lstore,lstore_<n>,fstore,fstore_<n>,dstore,dstore_<n>,astore,astore_<n>
3) Instrucciones para cargar constantes en la pila de operandos: bipush, sipush, ldc, ldc_w, ldc2_w, aconst_null, iconst_ml, iconst_<i>, lconst_<l>, fconst_<f>, dconst_<d>
4) Instrucción de índice de acceso a la tabla de variables locales: amplia
Algunas instrucciones que terminan con corchetes angulares representan un grupo de instrucciones, como iload_<i>, que representa iload_0, iload_1, etc. Estos grupos de instrucciones son instrucciones generales con un operando.
Instrucciones de operación
Las instrucciones aritméticas se utilizan para realizar una operación específica en los valores de las dos pilas de operandos y almacenar el resultado en la parte superior de la pila de operaciones.
1) Instrucciones de suma: iadd,ladd,fadd,dadd
2) Instrucciones de resta: isub, lsub, fsub, dsub
3) Instrucciones de multiplicación: imul, lmul, fmul, dmul
4) Instrucciones de división: idiv, ldiv, fdiv, ddiv
5) Resto de instrucciones: irem, lrem, frem, drem
6) Instrucciones negativas: ineg, longitud, fneg, dneg
7) Instrucciones de desplazamiento: ishl,ishr,iushr,lshl,lshr,lusr
8) Instrucciones OR bit a bit: ior, lor
9) Instrucciones AND bit a bit: iand, land
10) Instrucciones XOR bit a bit: ixor, lxor
11) Instrucción de incremento de variable local: iinc
12) Instrucciones de comparación: dcmpg, dcmpl, fcmpg, fcmpl, lcmp
La máquina virtual Java no estipula claramente el desbordamiento de datos enteros, pero estipula que al procesar datos enteros, solo las instrucciones de división y resto harán que la máquina virtual arroje una excepción cuando el divisor es 0.
instrucciones de carga y almacenamiento
Las instrucciones de carga y almacenamiento se utilizan para transferir datos hacia y desde la tabla de variables locales del marco de la pila y la pila de operandos.
1) Las instrucciones para cargar una variable local en la pila de operandos incluyen: iload, iload_<n>, lload, lload_<n>, float, fload_<n>, dload, dload_<n>, aload, aload_<n>.
2) Instrucciones para almacenar un valor de la pila de operandos en la variable local: istore,istore_<n>,lstore,lstore_<n>,fstore,fstore_<n>,dstore,dstore_<n>,astore,astore_<n>
3) Instrucciones para cargar constantes en la pila de operandos: bipush, sipush, ldc, ldc_w, ldc2_w, aconst_null, iconst_ml, iconst_<i>, lconst_<l>, fconst_<f>, dconst_<d>
4) Instrucción de índice de acceso a la tabla de variables locales: amplia
Algunas instrucciones que terminan con corchetes angulares representan un grupo de instrucciones, como iload_<i>, que representa iload_0, iload_1, etc. Estos grupos de instrucciones son instrucciones generales con un operando.
Instrucciones de operación
Las instrucciones aritméticas se utilizan para realizar una operación específica en los valores de las dos pilas de operandos y almacenar el resultado en la parte superior de la pila de operaciones.
1) Instrucciones de suma: iadd,ladd,fadd,dadd
2) Instrucciones de resta: isub, lsub, fsub, dsub
3) Instrucciones de multiplicación: imul, lmul, fmul, dmul
4) Instrucciones de división: idiv, ldiv, fdiv, ddiv
5) Resto de instrucciones: irem, lrem, frem, drem
6) Instrucciones negativas: ineg, longitud, fneg, dneg
7) Instrucciones de desplazamiento: ishl,ishr,iushr,lshl,lshr,lusr
8) Instrucciones OR bit a bit: ior, lor
9) Instrucciones AND bit a bit: iand, land
10) Instrucciones XOR bit a bit: ixor, lxor
11) Instrucción de incremento de variable local: iinc
12) Instrucciones de comparación: dcmpg, dcmpl, fcmpg, fcmpl, lcmp
La máquina virtual Java no estipula claramente el desbordamiento de datos enteros, pero estipula que al procesar datos enteros, solo las instrucciones de división y resto harán que la máquina virtual arroje una excepción cuando el divisor sea 0.
instrucciones de conversión de tipos
Las instrucciones de conversión de tipos convierten dos tipos numéricos de máquinas virtuales Java entre sí. Estas operaciones se utilizan generalmente para implementar operaciones de conversión de tipos explícitas en el código de usuario.
JVM admite conversión de tipo ampliada (conversión de tipo de rango pequeño a conversión de tipo de rango grande):
1) Tipo int a tipo largo, flotante, doble
2) tipo largo para flotar, tipo doble
3) flotar a tipo doble
Instrucciones de conversión de tipo estrecho: i2b, i2c, i2s, l2i, f2i, f2l, d2l y d2f. La conversión de tipo estrecho puede hacer que los resultados de la conversión produzcan diferentes signos y órdenes de magnitud. El proceso de conversión puede hacer que el valor numérico pierda precisión. Por ejemplo, cuando el tipo int o long se convierte al tipo entero T, el proceso de conversión solo descarta el contenido inesperado de los N bytes más bajos (N es la longitud del tipo de datos del tipo T)
Creación y manipulación de objetos.
Aunque las instancias de clase y las matrices son objetos, la máquina virtual Java utiliza diferentes instrucciones de código de bytes para la creación y operación de instancias de clase y matrices.
1) Instrucción para crear una instancia: nueva
2) Instrucciones para crear matrices: newarray, anewarray, multianewarray
3) Acceda a las instrucciones del campo: getfield, putfield, getstatic, putstatic
4) Cargue elementos de la matriz en las instrucciones de la pila de operandos: baload, caload, saload, iaload, laload, faload, daload, aaload
5) Almacene el valor de la pila de operandos en el elemento de la matriz y ejecute: bastore,castore,castore,sastore,iastore,fastore,dastore,aastore
6) Obtenga la instrucción de longitud de la matriz: longitud de la matriz
7) Verifique las instrucciones del tipo de instancia: instancia de, checkcast
Instrucciones de gestión de pila de operandos
Instrucciones que operan directamente la pila de operandos: pop, pop2, dup, dup2, dup_x1, dup2_x1, dup_x2, dup2_x2 y swap.
instrucción de transferencia de control
Permite que la JVM continúe condicional o incondicionalmente la ejecución del programa desde la instrucción que sigue a la instrucción especificada en lugar de la instrucción de transferencia de control. Las instrucciones de transferencia de control incluyen:
1) Rama condicional: ifeq, iflt, ifle, ifne, ifgt, ifge, ifnull, ifnotnull, if_cmpeq, if_icmpne, if_icmlt, if_icmpgt, etc.
2) Rama condicional compuesta: tablewitch, lookupswitch
3) Rama incondicional: goto,goto_w,jsr,jsr_w,ret
Hay una instrucción especial establecida en la JVM para manejar operaciones de comparación de ramas condicionales de tipos int y de referencia. Para indicar claramente si un valor de entidad es nulo, existen instrucciones especiales para detectar valores nulos. Las operaciones de comparación de ramas condicionales de tipo booleano y tipo byte, tipo char y tipo corto se completan utilizando instrucciones de comparación de tipo int, mientras que las operaciones de comparación de ramas condicionales largas, flotantes y dobles se realizan mediante instrucciones de operación de comparación del tipo correspondiente, y la operación Las instrucciones devolverán un El valor entero se agrega a la pila de operandos y luego se realiza una operación de comparación condicional de tipo int para completar todo el salto de rama. Todos los tipos de comparaciones eventualmente se convertirán en operaciones de comparación de tipo int.
Instrucciones de llamada y devolución del método.
Invocar instrucción virtual: llame al método de instancia del objeto y envíelo de acuerdo con el tipo real del objeto (despacho de máquina virtual).
Instrucción invokeinterface: llame al método de interfaz, busque un objeto que implemente este método de interfaz en tiempo de ejecución y encuentre el método apropiado para llamar.
invokespecial: llama a métodos de instancia que requieren procesamiento especial, incluidos métodos de inicialización de instancia, métodos privados y métodos de clase principal.
invokestatic: llamar al método de clase (estático)
Las instrucciones de retorno del método se distinguen según el tipo de valor de retorno, incluido ireturn (el valor de retorno es booleano, byte, char, short e int), lreturn, freturn, drturn y areturn. El otro retorno es para el método void, método de inicialización de instancia. , clase y Se utiliza el método de inicialización de clase i de la interfaz.
sincrónico
La JVM admite la sincronización a nivel de método y la sincronización de una secuencia de instrucciones dentro de un método, las cuales se implementan a través de monitores.
La sincronización a nivel de método es implícita y no necesita ser controlada mediante instrucciones de código de bytes. Se implementa en llamadas a métodos y operaciones de retorno. La máquina virtual distingue si es un método sincrónico a partir del indicador ACC_SYNCHRONIZED en la estructura estándar del método en el grupo constante de métodos. Cuando se llama al método, la instrucción de llamada verificará si el indicador está configurado. Si está configurado, el hilo de ejecución retiene el monitor, luego ejecuta el método y finalmente libera el monitor cuando se completa el método.
Sincroniza una secuencia de conjuntos de instrucciones, generalmente marcados por un bloque sincronizado. El conjunto de instrucciones JVM tiene monitorenter y monitorexit para admitir la semántica sincronizada.
El bloqueo estructurado se produce cuando cada salida del monitor durante una llamada a un método coincide con la entrada del monitor anterior. La JVM garantiza que los bloqueos estructurados se establezcan mediante las dos reglas siguientes (T representa un subproceso, M representa un monitor):
1) El número de veces que T retiene M durante la ejecución del método debe ser igual al número de veces que T libera M cuando se completa el método.
2) En ningún momento, nunca habrá una situación en la que T libere M más veces de las que T retenga M.