Uma instrução de máquina virtual Java consiste em um código de operação de comprimento de byte (Opcode) que representa um significado específico, seguido por zero ou mais operandos que representam os parâmetros de operação. Muitas instruções na máquina virtual não contêm operandos, apenas um opcode. Se a exceção for ignorada, o interpretador JVM poderá funcionar efetivamente com apenas um código.
Copie o código do código da seguinte forma:
fazer{
Calcule automaticamente o registro do PC e recupere o código de operação do local do registro do PC
if(operando existe) retira o operando;
Execute a operação definida pelo opcode;
}while (processa o próximo loop)
O número e o comprimento dos operandos dependem do opcode. Se o comprimento de um operando exceder um byte, ele será armazenado na ordem Big-Endian (primeiro bytecode de ponta) e seu valor deverá ser (byte1<<8) | byte2.
O fluxo de instruções de bytecode é alinhado por byte único, com exceção das instruções "tableswitch" e "lookupswitch". Seus operandos são especiais e são divididos por 4 bytes. Os espaços correspondentes precisam ser reservados para obter o alinhamento.
Limitar o comprimento do código de operação da máquina virtual Java a um byte e abrir mão do alinhamento do comprimento dos parâmetros do código compilado é obter um código compilado curto e enxuto, mesmo que isso possa custar à implementação da JVM um certo custo de desempenho. Como o opcode pode ter apenas um byte de comprimento, ele limita o número de conjuntos de instruções e não pressupõe que os dados estejam bem alinhados, o que significa que quando os dados excedem um byte, a estrutura de dados específica deve ser reconstruída a partir de os bytes. Algum desempenho será perdido.
Tipos de dados e máquina virtual Java
No conjunto de instruções na JVM, a maioria das instruções contém informações de tipo de dados correspondentes às suas operações. Por exemplo, a instrução iload carrega dados do tipo int da tabela de variáveis locais na pilha de operandos, enquanto fload carrega dados do tipo float.
Para a maioria das instruções de bytecode relacionadas a tipos de dados, seus mnemônicos de opcode possuem caracteres especiais para indicar: i representa o tipo int, l representa longo, s representa curto, b representa byte, c representa char, f significa float, d significa double e a significa referência. Existem diretivas separadas que podem ser usadas para converter tipos não suportados em tipos suportados quando necessário.
instruções de carregamento e armazenamento
As instruções de carregamento e armazenamento são usadas para transferir dados de e para a tabela de variáveis locais do quadro de pilha e a pilha de operandos.
1) As instruções para carregar uma variável local na pilha de operandos incluem: iload, iload_<n>, lload, lload_<n>, float, fload_<n>, dload, dload_<n>, aload, aload_<n>.
2) Instruções para armazenar um valor da pilha de operandos na variável local: istore,istore_<n>,lstore,lstore_<n>,fstore,fstore_<n>,dstore,dstore_<n>,astore,astore_<n>
3) Instruções para carregar constantes na pilha de operandos: bipush, sipush, ldc, ldc_w, ldc2_w, aconst_null, iconst_ml, iconst_<i>, lconst_<l>, fconst_<f>, dconst_<d>
4) Instrução de índice de acesso da tabela de variáveis locais: ampla
Algumas instruções que terminam com colchetes angulares representam um grupo de instruções, como iload_<i>, que representa iload_0, iload_1, etc. Esses grupos de instruções são instruções gerais com um operando.
Instruções de operação
As instruções aritméticas são usadas para realizar uma operação específica nos valores das duas pilhas de operandos e armazenar o resultado de volta no topo da pilha de operações.
1) Instruções de adição: iadd,ladd,fadd,dadd
2) Instruções de subtração: isub, lsub, fsub, dsub
3) Instruções de multiplicação: imul, lmul, fmul, dmul
4) Instruções de divisão: idiv, ldiv, fdiv, ddiv
5) Instruções restantes: irem, lrem, frem, drem
6) Instruções negativas: ineg, length, fneg, dneg
7) Instruções de deslocamento: ishl,ishr,iushr,lshl,lshr,lusr
8) Instruções OR bit a bit: ior, lor
9) Instruções AND bit a bit: iand, land
10) Instruções XOR bit a bit: ixor, lxor
11) Instrução de incremento de variável local: iinc
12) Instruções de comparação: dcmpg, dcmpl, fcmpg, fcmpl, lcmp
A máquina virtual Java não estipula claramente o estouro de dados inteiros, mas estipula que, ao processar dados inteiros, apenas as instruções de divisão e resto farão com que a máquina virtual lance uma exceção quando o divisor for 0.
instruções de carregamento e armazenamento
As instruções de carregamento e armazenamento são usadas para transferir dados de e para a tabela de variáveis locais do quadro de pilha e da pilha de operandos.
1) As instruções para carregar uma variável local na pilha de operandos incluem: iload, iload_<n>, lload, lload_<n>, float, fload_<n>, dload, dload_<n>, aload, aload_<n>.
2) Instruções para armazenar um valor da pilha de operandos na variável local: istore,istore_<n>,lstore,lstore_<n>,fstore,fstore_<n>,dstore,dstore_<n>,astore,astore_<n>
3) Instruções para carregar constantes na pilha de operandos: bipush, sipush, ldc, ldc_w, ldc2_w, aconst_null, iconst_ml, iconst_<i>, lconst_<l>, fconst_<f>, dconst_<d>
4) Instrução de índice de acesso da tabela de variáveis locais: ampla
Algumas instruções que terminam com colchetes angulares representam um grupo de instruções, como iload_<i>, que representa iload_0, iload_1, etc. Esses grupos de instruções são instruções gerais com um operando.
Instruções de operação
As instruções aritméticas são usadas para realizar uma operação específica nos valores das duas pilhas de operandos e armazenar o resultado de volta no topo da pilha de operações.
1) Instruções de adição: iadd,ladd,fadd,dadd
2) Instruções de subtração: isub, lsub, fsub, dsub
3) Instruções de multiplicação: imul, lmul, fmul, dmul
4) Instruções de divisão: idiv, ldiv, fdiv, ddiv
5) Instruções restantes: irem, lrem, frem, drem
6) Instruções negativas: ineg, length, fneg, dneg
7) Instruções de deslocamento: ishl,ishr,iushr,lshl,lshr,lusr
8) Instruções OR bit a bit: ior, lor
9) Instruções AND bit a bit: iand, land
10) Instruções XOR bit a bit: ixor, lxor
11) Instrução de incremento de variável local: iinc
12) Instruções de comparação: dcmpg, dcmpl, fcmpg, fcmpl, lcmp
A máquina virtual Java não estipula claramente o estouro de dados inteiros, mas estipula que, ao processar dados inteiros, apenas as instruções de divisão e resto farão com que a máquina virtual lance uma exceção quando o divisor for 0.
digite instruções de conversão
As instruções de conversão de tipo convertem dois tipos numéricos de máquinas virtuais Java entre si. Essas operações geralmente são usadas para implementar operações explícitas de conversão de tipo no código do usuário.
A JVM suporta conversão de tipo ampliada (conversão de tipo de intervalo pequeno para conversão de tipo de intervalo grande):
1) Tipo Int para tipo longo, flutuante, duplo
2) tipo longo para flutuar, tipo duplo
3) float para tipo duplo
Instruções de conversão de tipo estreito: i2b, i2c, i2s, l2i, f2i, f2l, d2l e d2f. A conversão de tipo estreito pode fazer com que os resultados da conversão produzam sinais e ordens de magnitude diferentes. Por exemplo, quando o tipo int ou long é convertido para o tipo inteiro T, o processo de conversão descarta apenas o conteúdo inesperado dos N bytes mais baixos (N é o comprimento do tipo de dados do tipo T)
Criação e manipulação de objetos
Embora as instâncias e matrizes de classe sejam objetos, a máquina virtual Java usa instruções de bytecode diferentes para a criação e operação de instâncias e matrizes de classe.
1) Instrução para criar uma instância: nova
2) Instruções para criação de arrays: newarray, anewarray, multianewarray
3) Acesse instruções de campo: getfield, putfield, getstatic, putstatic
4) Carregar elementos da matriz nas instruções da pilha de operandos: baload, caload, saload, iaload, laload, faload, daload, aaload
5) Armazene o valor da pilha de operandos no elemento do array e execute: bastore,castore,castore,sastore,iastore,fastore,dastore,aastore
6) Obtenha a instrução de comprimento do array: arraylength
7) Verifique as instruções do tipo de instância: instanceof, checkcast
Instruções de gerenciamento de pilha de operandos
Instruções que operam diretamente a pilha de operandos: pop, pop2, dup, dup2, dup_x1, dup2_x1, dup_x2, dup2_x2 e swap
instrução de transferência de controle
Permite que a JVM continue condicional ou incondicionalmente a execução do programa a partir da instrução que segue a instrução especificada, em vez da instrução de transferência de controle. As instruções de transferência de controle incluem:
1) Ramificação condicional: ifeq, iflt, ifle, ifne, ifgt, ifge, ifnull, ifnotnull, if_cmpeq, if_icmpne, if_icmlt, if_icmpgt, etc.
2) Ramo condicional composto: tablewitch, lookupswitch
3) Ramificação incondicional: goto,goto_w,jsr,jsr_w,ret
Há um conjunto de instruções especial na JVM para lidar com operações de comparação de ramificação condicional dos tipos int e de referência. Para indicar claramente se um valor de entidade é nulo, existem instruções especiais para detectar valores nulos. As operações de comparação de ramificação condicional do tipo booleano e do tipo byte, tipo char e tipo curto são todas concluídas usando instruções de comparação do tipo int, enquanto as operações de comparação de ramificação condicional longa, flutuante e dupla são executadas por instruções de operação de comparação do tipo correspondente, e a operação as instruções retornarão um O valor inteiro é adicionado à pilha de operandos e, em seguida, uma operação de comparação condicional do tipo int é executada para completar todo o salto de ramificação. Todos os tipos de comparações serão eventualmente convertidos em operações de comparação do tipo int.
Chamada de método e instruções de retorno
Instrução Invokevirtual: Chame o método de instância do objeto e despache de acordo com o tipo real do objeto (despacho da máquina virtual).
Instrução InvokeInterface: Chame o método de interface, procure um objeto que implemente esse método de interface em tempo de execução e encontre o método apropriado para chamar.
invocaespecial: Chame métodos de instância que requerem processamento especial, incluindo métodos de inicialização de instância, métodos privados e métodos de classe pai
invocastatic: chama o método da classe (estático)
As instruções de retorno do método são diferenciadas de acordo com o tipo de valor de retorno, incluindo ireturn (o valor de retorno é booleano, byte, char, short e int), lreturn, freturn, drturn e areturn. O outro retorno é para o método void, método de inicialização de instância. , classe e O método i de inicialização de classe da interface é usado.
síncrono
A JVM suporta sincronização em nível de método e sincronização de uma sequência de instruções dentro de um método, ambas implementadas por meio de monitores.
A sincronização em nível de método é implícita e não precisa ser controlada por meio de instruções de bytecode. Ela é implementada em chamadas de método e operações de retorno. A máquina virtual distingue se é um método síncrono do sinalizador ACC_SYNCHRONIZED na estrutura padrão do método no conjunto de constantes do método. Quando o método é chamado, a instrução de chamada verificará se o sinalizador está definido. Se estiver definido, o thread de execução mantém o monitor, executa o método e, finalmente, libera o monitor quando o método é concluído.
Sincronize uma sequência de conjuntos de instruções, geralmente marcados por um bloco sincronizado. O conjunto de instruções JVM possui monitorenter e monitorexit para suportar a semântica sincronizada.
O bloqueio estruturado ocorre quando cada saída de monitor durante uma chamada de método corresponde à entrada de monitor anterior. A JVM garante que os bloqueios estruturados sejam estabelecidos por meio das duas regras a seguir (T representa um encadeamento, M representa um monitor):
1) O número de vezes que T retém M durante a execução do método deve ser igual ao número de vezes que T libera M quando o método é concluído.
2) Em nenhum momento, nunca haverá uma situação em que T libere M mais vezes do que T segure M.