Bytecode Java

Introdução à arquitetura e design de software

Todo desenvolvedor Java logo aprende que o código-fonte Java é compilado e gera bytecodes que formam o executável Java. Mas o que são exatamente esses bytecodes e o que fazem já é um ponto que muitos desenvolvedores ignoram.

Quando trabalhamos com uma tecnologia é sempre útil entender melhor sua base, seus fundamentos. Com o Java, conhecer um pouco mais dos bytecodes pode ajudar em alguns momentos.

Por isso, segue abaixo uma explicação sobre o bytecode Java e exemplo comentado.

Bytecodes da Máquina Virtual Java

Ao compilar nosso código-fonte Java, é gerado um arquivo com extensão .class contendo diversos bytecodes. Assim como o código compilado da linguagem C, por exemplo, gera código de máquina, esses bytecodes do código compilado Java também são linguagem de máquina. 

A grande diferença aqui é que quando desenvolvemos em C, devemos compilar nosso código-fonte com um compilador específico para cada sistema operacional, que irá gerar código específico para aquela máquina. No mundo Java, por sua vez, temos a necessidade de compilar apenas uma vez, gerando código para uma máquina virtual Java (JVM). Daí vem o mantra Java “code once, run everywhere” (codifique uma vez, rode em qualquer lugar), que ilustra a característica Java de ser cross-platform.

Essa idéia da JVM torna o Java uma plataforma extremamente poderosa. Se for possível criar uma JVM para algum tipo de máquina, será possível rodar, na teoria, qualquer código Java que você tenha escrito. Com isso, é possível termos o Java rodando em diversos sistemas operacionais, dispositivos móveis, etc.

A JVM nada mais é do que uma ponte, que executa as instruções de máquina genéricas (bytecodes) compiladas a partir do código-fonte Java, análogas aos mnemônicos do assembly, e as traduz para as instruções específicas do sistema operacional e hardaware utilizados.

O código compilado Java é formado por instruções conhecidas como opcodes com o tamanho de 1 byte, vindo daí o nome bytecodes.

Como cada bytecode possui 1 byte, é possível representar 256 bytecodes diferentes (embora a linguagem possua atualmente apenas 205): http://en.wikipedia.org/wiki/Java_bytecode_instruction_listings.
Para gerar bytecodes Java a partir de um código-fonte existe o comando javac, um executável distribuído junto com a JDK. E, se quisermos fazer o processo contrário; analisar de forma mais legível o .class (compilado) Java, podemos usar o comando javap, também distribuído com a JDK.

Exemplo

Código-fonte Java

Abaixo temos um código simples Java, um laço de repetição:

outer:
  for (int i = 2; i < 1000; i++) {
      for (int j = 2; j < i; j++) {
          if (i % j == 0)
              continue outer;
      }
      System.out.println (i);
  }

Bytecodes

Ao compilarmos o código acima, os bytecodes gerados seriam mais ou menos como os abaixo:

  
  0:   iconst_2				 # carrega valor literal '2' na pilha
  1:   istore_1				 # armazena valor carregado anteriormente na variável 1 (variável i)
  2:   iload_1	    	  # carrega a variável 1 (variável i) na pilha
  3:   sipush 1000			# carrega valor literal '1000' (short) na pilha
  6:   if_icmpge 44		 # se primeiro valor carregado (variável i) for maior ou igual ao segundo valor carregado (literal '1000'), vai para linha 44 (return)
  9:   iconst_2				 # carrega valor literal '2' na pilha 
  10:  istore_2				 # armazena valor carregado anteriormente na variável 2 (j)
  11:  iload_2				  # carreja variável 2 (variável j) na pilha
  12:  iload_1				  # carrega variável 1 (variável i) na pilha
  13:  if_icmpge 31		 # se primeiro valor carregado (variável j) for maior ou igual ao segundo valor carregado (variável i), vai para linha 32 (fora do loop)
  16:  iload_1			    # carrega variável 1 (variável i) na pilha
  17:  iload_2			    # carrega variável 2 (variável j) na pilha
  18:  irem			       # faz módulo (resto) dos dois valores carregados na memória (i % j)		
  19:  ifne 25	        # se resultado anterior não for zero, vai para linha 25 (loop interno)
  22:  goto 38	        # se não saiu do loop (módulo é zero), vai para linha 38 (label outer)
  25:  iinc 2, 1	      # incrementa variável da segunda posição da memória (variável j) de 1
  28:  goto 11	        # vai para linha 11 (loop interno)
  31:  getstatic #84; //Field java/lang/System.out:Ljava/io/PrintStream; 
                        # carrega um valor estático de uma classe (variável System.out)
  34:  iload_1			    # carrega variável 1 (variável i) na pilha 
  35:  invokevirtual #85; //Method java/io/PrintStream.println:(I)V,                               
                        # invoca método println com parametro sendo o valor carregado na pilha (variável i)
  38:  iinc 1, 1		    # incrementa variável da primeira posição da memória (variável i) de 1
  41:  goto 2		       # vai para linha 2 (loop externo)
  44:  return			     # retorno void do método

Referências

Livro “Introdução à arquitetura e design de software: uma visão sobre a plataforma Java” – Paulo Silveira, Guilherme Silveira, Sérgio Lopes, Guilherme Moreira, Nico Steppat, Fabio Kung
Artigo “Java Bytecode” – Wikipedia

 

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>